Merge remote-tracking branch 'origin/master' into release/next-sys

This commit is contained in:
Philip Monk 2021-07-20 13:57:50 -04:00
commit 5996b3aa1e
No known key found for this signature in database
GPG Key ID: B66E1F02604E44EC
318 changed files with 57228 additions and 44222 deletions

3
.eslintrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
ignorePatterns: ["**/*"]
};

View File

@ -1,4 +1,4 @@
FROM jaredtobin/janeway:v0.15.3.1 FROM tloncorp/janeway:v0.15.4
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
EXPOSE 22/tcp EXPOSE 22/tcp
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]

View File

@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- run: cd 'pkg/interface' && npm i - run: npm i && npm run bootstrap
- name: Publish to Chromatic - name: Publish to Chromatic
uses: chromaui/action@v1 uses: chromaui/action@v1
with: with:

24
.github/workflows/frontend-test.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: frontend-test
on:
pull_request:
paths:
- 'pkg/interface/**'
- 'pkg/btc-wallet/**'
- 'pkg/npm/**'
jobs:
frontend-test:
runs-on: ubuntu-latest
name: "Test changed frontend packages"
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- run: git fetch --prune
- name: 'Setup root deps'
run: npm ci
- name: 'Setup dependencies'
run: npm run bootstrap
- name: 'Run tests'
run: npm run test -- --since origin/$GITHUB_BASE_REF --include-dependents

View File

@ -1,14 +0,0 @@
name: typescript-check
on:
pull_request:
paths:
- 'pkg/interface/**'
jobs:
typescript-check:
runs-on: ubuntu-latest
name: "Check pkg/interface types"
steps:
- uses: actions/checkout@v2
- run: cd 'pkg/interface' && npm i && npm run tsc

1
.gitignore vendored
View File

@ -33,6 +33,7 @@ result-*
# NodeJS # NodeJS
node_modules node_modules
.eslintcache
# Haskell # Haskell
.stack-work .stack-work

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

8
.husky/pre-commit Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
command -v npx > /dev/null || {
exit 0
}
npx lint-staged

8
lerna.json Normal file
View File

@ -0,0 +1,8 @@
{
"packages": [
"pkg/npm/*",
"pkg/btc-wallet",
"pkg/interface"
],
"version": "independent"
}

6915
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "root",
"private": true,
"devDependencies": {
"eslint": "^7.29.0",
"husky": "^6.0.0",
"lerna": "^4.0.0",
"lint-staged": "^11.0.0"
},
"scripts": {
"watch-libs": "lerna run watch --no-private --parallel",
"build-libs": "lerna run build --no-private",
"test": "lerna run test",
"prepare": "husky install .husky",
"bootstrap": "lerna bootstrap",
"build:prod": "lerna run build:prod"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix"
}
}

View File

@ -11,21 +11,25 @@
:: ::
/- *bitcoin, json-rpc, *btc-provider /- *bitcoin, json-rpc, *btc-provider
/+ dbug, default-agent, bl=btc, groupl=group, resource /+ dbug, default-agent, bl=btc, groupl=group, resource
~% %btc-provider-top ..part ~
|% |%
+$ card card:agent:gall
+$ versioned-state +$ versioned-state
$% state-0 $% state-0
state-1
state-2
== ==
:: ::
+$ state-0 [%0 =host-info =whitelist] +$ state-0 [%0 =host-info =whitelist]
:: +$ state-1 [%1 =host-info =whitelist timer=(unit @da)]
+$ card card:agent:gall +$ state-2 [%2 =host-info =whitelist timer=(unit @da) interval=@dr]
::
-- --
%- agent:dbug %- agent:dbug
=| state-0 =| state-2
=* state - =* state -
^- agent:gall ^- agent:gall
=< =<
~% %btc-provider-agent ..send-status ~
|_ =bowl:gall |_ =bowl:gall
+* this . +* this .
def ~(. (default-agent this %|) bowl) def ~(. (default-agent this %|) bowl)
@ -33,13 +37,13 @@
:: ::
++ on-init ++ on-init
^- (quip card _this) ^- (quip card _this)
~& > '%btc-provider initialized successfully'
=| wl=^whitelist =| wl=^whitelist
:- ~ :- ~
%_ this %_ this
host-info host-info ['' connected=%.n %main block=0 clients=*(set ship)]
['' connected=%.n %main block=0 clients=*(set ship)]
whitelist wl(public %.n, kids %.n) whitelist wl(public %.n, kids %.n)
timer ~
interval ~m1
== ==
:: ::
++ on-save ++ on-save
@ -49,107 +53,73 @@
++ on-load ++ on-load
|= old-state=vase |= old-state=vase
^- (quip card _this) ^- (quip card _this)
~& > '%btc-provider recompiled successfully ' =/ old !<(versioned-state old-state)
`this(state !<(versioned-state old-state)) ?- -.old
%2
[~ this(state old)]
::
%1
`this(state [%2 host-info.old whitelist.old timer.old ~m1])
::
%0
:_ this(state [%2 host-info.old whitelist.old ~ ~m1])
?: =('' api-url.host-info.old) ~
~[(start-ping-timer:hc ~s0)]
==
:: ::
++ on-poke ++ on-poke
~/ %on-poke
|= [=mark =vase] |= [=mark =vase]
^- (quip card _this) ^- (quip card _this)
|^
?> ?|((team:title our.bowl src.bowl) (is-client:hc src.bowl)) ?> ?|((team:title our.bowl src.bowl) (is-client:hc src.bowl))
=^ cards state =^ cards state
?+ mark (on-poke:def mark vase) ?+ mark (on-poke:def mark vase)
%btc-provider-command %btc-provider-command
?> (team:title our.bowl src.bowl) ?> (team:title our.bowl src.bowl)
(handle-command:hc !<(command vase)) (handle-command !<(command vase))
::
%btc-provider-action %btc-provider-action
(handle-action:hc !<(action vase)) (handle-action !<(action vase))
::
%noun
?. =(q.vase %kick-timer) `state
:_ state(timer `now.bowl)
:* (start-ping-timer ~s0)
?~ timer ~
[[%pass /block-time %arvo %b %rest u.timer] ~]
==
== ==
[cards this] [cards this]
::
++ on-watch
|= pax=path
^- (quip card _this)
:: checking provider permissions before trying to subscribe
:: terrible hack until we have cross-ship scries
:: ::
?: ?=([%permitted @ ~] pax) ++ handle-command
:_ this
=/ jon=json
%+ frond:enjs:format
%'providerStatus'
%- pairs:enjs:format
:~ provider+s+(scot %p our.bowl)
permitted+b+(is-whitelisted:hc src.bowl)
==
[%give %fact ~ %json !>(jon)]~
::
?> ?=([%clients *] pax)
?. (is-whitelisted:hc src.bowl)
~|("btc-provider: blocked client {<src.bowl>}" !!)
~& > "btc-provider: accepted client {<src.bowl>}"
:- [do-ping:hc]~
this(clients.host-info (~(put in clients.host-info) src.bowl))
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
:: check for connectivity every 30 seconds
::
?: ?=([%ping-timer *] wire)
:_ this
:~ do-ping:hc
(start-ping-timer:hc ~s30)
==
=^ cards state
?+ +<.sign-arvo (on-arvo:def wire sign-arvo)
%http-response
(handle-rpc-response:hc wire client-response.sign-arvo)
==
[cards this]
::
++ on-peek
|= pax=path
^- (unit (unit cage))
?+ pax (on-peek:def pax)
[%x %is-whitelisted @t ~]
``noun+!>((is-whitelisted:hc (ship (slav %p +>-.pax))))
::
[%x %is-client @t ~]
``noun+!>((is-client (ship (slav %p +>-.pax))))
==
::
++ on-leave on-leave:def
++ on-agent on-agent:def
++ on-fail on-fail:def
--
:: helper core
|_ =bowl:gall
++ handle-command
|= comm=command |= comm=command
^- (quip card _state) ^- (quip card _state)
?- -.comm ?- -.comm
%set-credentials %set-credentials
:- :~ do-ping :_ %_ state
(start-ping-timer ~s30) host-info [api-url.comm %.n network.comm 0 *(set ship)]
timer `now.bowl
== ==
%= state :* (start-ping-timer:hc ~s0)
host-info ?~ timer ~
[api-url.comm connected=%.n network.comm block=0 clients=*(set ship)] [[%pass /block-time %arvo %b %rest u.timer] ~]
== ==
:: ::
%add-whitelist %add-whitelist
:- ~
?- -.wt.comm ?- -.wt.comm
%public %public
`state(public.whitelist %.y) state(public.whitelist %.y)
:: ::
%kids %kids
`state(kids.whitelist %.y) state(kids.whitelist %.y)
:: ::
%users %users
`state(users.whitelist (~(uni in users.whitelist) users.wt.comm)) state(users.whitelist (~(uni in users.whitelist) users.wt.comm))
:: ::
%groups %groups
`state(groups.whitelist (~(uni in groups.whitelist) groups.wt.comm)) state(groups.whitelist (~(uni in groups.whitelist) groups.wt.comm))
== ==
:: ::
%remove-whitelist %remove-whitelist
@ -168,62 +138,119 @@
state(groups.whitelist (~(dif in groups.whitelist) groups.wt.comm)) state(groups.whitelist (~(dif in groups.whitelist) groups.wt.comm))
== ==
clean-client-list clean-client-list
::
%set-interval
`state(interval inte.comm)
== ==
:: if not connected, only %ping action is allowed ::
:: :: +clean-client-list: remove clients who are no longer whitelisted
++ handle-action :: called after a whitelist change
::
++ clean-client-list
^- (quip card _state)
=/ to-kick=(set ship)
%- silt
%+ murn ~(tap in clients.host-info)
|= c=ship ^- (unit ship)
?:((is-whitelisted:hc c) ~ `c)
:_ state(clients.host-info (~(dif in clients.host-info) to-kick))
%+ turn ~(tap in to-kick)
|=(c=ship [%give %kick ~[/clients] `c])
::
:: if not connected, only %ping action is allowed
::
++ handle-action
|= act=action |= act=action
^- (quip card _state) ^- (quip card _state)
:_ state
?. ?|(connected.host-info ?=(%ping -.act)) ?. ?|(connected.host-info ?=(%ping -.act))
~& >>> "Not connected to RPC" ~[(send-update:hc [%| %not-connected 500] ~)]
[~[(send-update [%| %not-connected 500])] state] :_ ~
:: %+ req-card act
=/ ract=action:rpc-types ^- action:rpc-types
?- -.act :: ~|("Invalid action" !!) ?- -.act
%address-info %address-info [%get-address-info address.act]
[%get-address-info address.act] %tx-info [%get-tx-vals txid.act]
:: %raw-tx [%get-raw-tx txid.act]
%tx-info %broadcast-tx [%broadcast-tx rawtx.act]
[%get-tx-vals txid.act] %ping [%get-block-info ~]
:: %block-info [%get-block-info block.act]
%raw-tx
[%get-raw-tx txid.act]
::
%broadcast-tx
[%broadcast-tx rawtx.act]
::
%ping
[%get-block-info ~]
::
%block-info
[%get-block-info block.act]
== ==
[~[(req-card act ract)] state] ::
:: ++ req-card
++ req-card
|= [act=action ract=action:rpc-types] |= [act=action ract=action:rpc-types]
=| out=outbound-config:iris
=/ req=request:http =/ req=request:http
(gen-request:bl host-info ract) (gen-request:bl host-info ract)
[%pass (rpc-wire act) %arvo %i %request req out] [%pass (rpc-wire act) %arvo %i %request req *outbound-config:iris]
:: wire structure: /action-tas/now ::
++ rpc-wire
|= act=action
^- wire
/[-.act]/(scot %p src.bowl)/(scot %ux (cut 3 [0 20] eny.bowl))
--
:: ::
++ rpc-wire ++ on-watch
|= act=action ^- wire ~/ %on-watch
/[-.act]/[(scot %ux (cut 3 [0 20] eny.bowl))] |= pax=path
^- (quip card _this)
:: checking provider permissions before trying to subscribe
:: terrible hack until we have cross-ship scries
::
?: ?=([%permitted @ ~] pax)
:_ this
=/ jon=json
%+ frond:enjs:format
%'providerStatus'
%- pairs:enjs:format
:~ provider+s+(scot %p our.bowl)
permitted+b+(is-whitelisted:hc src.bowl)
==
[%give %fact ~ %json !>(jon)]~
::
?> ?| ?=([%clients ~] pax)
?& ?=([%clients @ ~] pax)
=(src.bowl (slav %p i.t.pax))
==
==
?. (is-whitelisted:hc src.bowl)
~|("btc-provider: blocked client {<src.bowl>}" !!)
~& > "btc-provider: accepted client {<src.bowl>}"
`this(clients.host-info (~(put in clients.host-info) src.bowl))
:: ::
++ kick-client ++ on-arvo
|= client=ship ~/ %on-arvo
^- (quip card _state) |= [wir=wire =sign-arvo]
~& >>> "dropping client {<client>}" |^
:- ~[[%give %kick ~[/clients] `client]] ^- (quip card _this)
state(clients.host-info (~(dif in clients.host-info) (silt ~[client]))) :: check for connectivity every 30 seconds
:: ::
:: Handles HTTP responses from RPC servers. Parses for errors, then handles response. ?: ?=([%ping-timer *] wir)
:: For actions that require collating multiple RPC calls, uses req-card to call out `this
:: to RPC again if more information is required. ?: ?=([%block-ping *] wir)
:: :_ this(timer `(add now.bowl interval))
++ handle-rpc-response :~ do-ping
(start-ping-timer:hc interval)
==
=^ cards state
?+ +<.sign-arvo (on-arvo:def wir sign-arvo)
%http-response
(handle-rpc-response wir client-response.sign-arvo)
==
[cards this]
::
++ do-ping
^- card
=/ act=action [%ping ~]
:* %pass /ping/[(scot %da now.bowl)] %agent
[our.bowl %btc-provider] %poke
%btc-provider-action !>(act)
==
::
:: Handles HTTP responses from RPC servers. Parses for errors,
:: then handles response. For actions that require collating multiple
:: RPC calls, uses req-card to call out to RPC again if more
:: information is required.
++ handle-rpc-response
|= [=wire response=client-response:iris] |= [=wire response=client-response:iris]
^- (quip card _state) ^- (quip card _state)
?. ?=(%finished -.response) `state ?. ?=(%finished -.response) `state
@ -234,84 +261,111 @@
(connection-error status) (connection-error status)
?^ conn-err ?^ conn-err
:_ state(connected.host-info %.n) :_ state(connected.host-info %.n)
~[(send-status [%disconnected ~]) (send-update [%| u.conn-err])] :~ (send-status:hc [%disconnected ~] ~)
(send-update:hc [%| u.conn-err] ~)
==
:: ::
%+ handle-rpc-result wire %+ handle-rpc-result wire
%- parse-result:rpc:bl %- parse-result:rpc:bl
(get-rpc-response:bl response) (get-rpc-response:bl response)
:: ::
++ connection-error ++ handle-rpc-result
|= status=@ud
^- [(unit error) _state]
?+ status [`[%rpc-error ~] state]
%200
[~ state]
%400
[`[%bad-request status] state]
%401
[`[%no-auth status] state(connected.host-info %.n)]
%502
[`[%not-connected status] state(connected.host-info %.n)]
%504
[`[%not-connected status] state(connected.host-info %.n)]
==
::
++ handle-rpc-result
|= [=wire r=result:rpc-types] |= [=wire r=result:rpc-types]
^- (quip card _state) ^- (quip card _state)
=/ ship=(unit ship)
(slaw %p (snag 1 wire))
?+ -.wire ~|("Unexpected HTTP response" !!) ?+ -.wire ~|("Unexpected HTTP response" !!)
%address-info %address-info
?> ?=([%get-address-info *] r) ?> ?=([%get-address-info *] r)
:_ state :_ state
~[(send-update [%.y %address-info +.r])] ~[(send-update:hc [%.y %address-info +.r] ship)]
:: ::
%tx-info %tx-info
?> ?=([%get-tx-vals *] r) ?> ?=([%get-tx-vals *] r)
:_ state :_ state
~[(send-update [%.y %tx-info +.r])] ~[(send-update:hc [%.y %tx-info +.r] ship)]
:: ::
%raw-tx %raw-tx
?> ?=([%get-raw-tx *] r) ?> ?=([%get-raw-tx *] r)
:_ state :_ state
~[(send-update [%.y %raw-tx +.r])] ~[(send-update:hc [%.y %raw-tx +.r] ship)]
:: ::
%broadcast-tx %broadcast-tx
?> ?=([%broadcast-tx *] r) ?> ?=([%broadcast-tx *] r)
:_ state :_ state
~[(send-update [%.y %broadcast-tx +.r])] ~[(send-update:hc [%.y %broadcast-tx +.r] ship)]
:: ::
%ping %ping
?> ?=([%get-block-info *] r) ?> ?=([%get-block-info *] r)
:_ state(connected.host-info %.y, block.host-info block.r) :_ state(connected.host-info %.y, block.host-info block.r)
:_ ~
%- send-status:hc
:_ ~
?: =(block.host-info block.r) ?: =(block.host-info block.r)
~[(send-status [%connected network.host-info block.r fee.r])] [%connected network.host-info block.r fee.r]
~[(send-status [%new-block network.host-info block.r fee.r blockhash.r blockfilter.r])] [%new-block network.host-info block.r fee.r blockhash.r blockfilter.r]
:: ::
%block-info %block-info
?> ?=([%get-block-info *] r) ?> ?=([%get-block-info *] r)
:_ state :_ state
~[(send-update [%.y %block-info network.host-info +.r])] ~[(send-update:hc [%.y %block-info network.host-info +.r] ship)]
== ==
::
++ connection-error
|= status=@ud
^- [(unit error) _state]
?+ status [`[%rpc-error ~] state]
%200 [~ state]
%400 [`[%bad-request status] state]
%401 [`[%no-auth status] state(connected.host-info %.n)]
%502 [`[%not-connected status] state(connected.host-info %.n)]
%504 [`[%not-connected status] state(connected.host-info %.n)]
==
--
:: ::
++ on-peek
~/ %on-peek
|= pax=path
^- (unit (unit cage))
?+ pax (on-peek:def pax)
[%x %is-whitelisted @t ~]
``noun+!>((is-whitelisted:hc (ship (slav %p +>-.pax))))
::
[%x %is-client @t ~]
``noun+!>((is-client:hc (ship (slav %p +>-.pax))))
==
::
++ on-leave on-leave:def
++ on-agent on-agent:def
++ on-fail on-fail:def
--
:: helper core
~% %btc-provider-helper ..card ~
|_ =bowl:gall
++ send-status ++ send-status
|= =status ^- card |= [=status ship=(unit ship)]
^- card
%- ?: ?=(%new-block -.status) %- ?: ?=(%new-block -.status)
~&(>> "%new-block: {<block.status>}" same) ~&(>> "%new-block: {<block.status>}" same)
same same
[%give %fact ~[/clients] %btc-provider-status !>(status)] =- [%give %fact ~[-] %btc-provider-status !>(status)]
?~ ship /clients
/clients/(scot %p u.ship)
:: ::
++ send-update ++ send-update
|= =update |= [=update ship=(unit ship)]
^- card ^- card
=+ c=[%give %fact ~[/clients] %btc-provider-update !>(update)] %- ?: ?=(%.y -.update)
?: ?=(%.y -.update) same
:: ~& >> "prov. update: {<p.update>}" ~&(>> "prov. err: {<p.update>}" same)
c =- [%give %fact ~[-] %btc-provider-update !>(update)]
~& >> "prov. err: {<p.update>}" ?~ ship /clients
c /clients/(scot %p u.ship)
:: ::
++ is-whitelisted ++ is-whitelisted
|= user=ship ^- ? ~/ %is-whitelisted
|= user=ship
^- ?
|^ |^
?| public.whitelist ?| public.whitelist
=(our.bowl user) =(our.bowl user)
@ -319,8 +373,10 @@
(~(has in users.whitelist) user) (~(has in users.whitelist) user)
in-group in-group
== ==
::
++ is-kid ++ is-kid
=(our.bowl (sein:title our.bowl now.bowl user)) =(our.bowl (sein:title our.bowl now.bowl user))
::
++ in-group ++ in-group
=/ gs ~(tap in groups.whitelist) =/ gs ~(tap in groups.whitelist)
|- |-
@ -328,35 +384,15 @@
?: (~(is-member groupl bowl) user i.gs) ?: (~(is-member groupl bowl) user i.gs)
%.y %.y
$(gs t.gs) $(gs t.gs)
:: .^((unit group:g) %gx ;:(weld /=group-store=/groups p /noun))
-- --
:: +clean-client-list: remove clients who are no longer whitelisted
:: called after a whitelist change
::
++ clean-client-list
^- (quip card _state)
=/ to-kick=(set ship)
%- silt
%+ murn ~(tap in clients.host-info)
|= c=ship ^- (unit ship)
?:((is-whitelisted c) ~ `c)
:_ state(clients.host-info (~(dif in clients.host-info) to-kick))
%+ turn ~(tap in to-kick)
|=(c=ship [%give %kick ~[/clients] `c])
:: ::
++ is-client ++ is-client
|= user=ship ^- ? |= user=ship
^- ?
(~(has in clients.host-info) user) (~(has in clients.host-info) user)
:: ::
++ start-ping-timer ++ start-ping-timer
|= interval=@dr ^- card |= interval=@dr
[%pass /ping-timer %arvo %b %wait (add now.bowl interval)]
::
++ do-ping
^- card ^- card
=/ act=action [%ping ~] [%pass /block-ping %arvo %b %wait (add now.bowl interval)]
:* %pass /ping/[(scot %da now.bowl)] %agent
[our.bowl %btc-provider] %poke
%btc-provider-action !>(act)
==
-- --

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,6 @@
<div id="portal-root"></div> <div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script> <script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script> <script src="/~landscape/js/session.js"></script>
<script src="/~btc/js/bundle/index.f7ab13b7db3ec1f8b55a.js"></script> <script src="/~btc/js/bundle/index.3e8bcc150ebd820dd3b2.js"></script>
</body> </body>
</html> </html>

View File

@ -12,8 +12,8 @@
[%glob =glob:glob] [%glob =glob:glob]
== ==
:: ::
+$ state-3 +$ state-4
$: %3 $: %4
=configuration:srv =configuration:srv
=serving =serving
== ==
@ -22,7 +22,7 @@
%+ verb | %+ verb |
%- agent:dbug %- agent:dbug
:: ::
=| state-3 =| state-4
=* state - =* state -
^- agent:gall ^- agent:gall
|_ =bowl:gall |_ =bowl:gall
@ -42,6 +42,7 @@
== ==
:~ (connect /) :~ (connect /)
(connect /'~landscape') (connect /'~landscape')
[%pass /serve-who %arvo %e %serve [~ /who] %home /gen/who/hoon ~]
== ==
:: ::
++ connect ++ connect
@ -56,6 +57,7 @@
^- (quip card _this) ^- (quip card _this)
|^ |^
=+ !<(old-state=versioned-state old-vase) =+ !<(old-state=versioned-state old-vase)
=| cards=(list card)
=? old-state ?=(%0 -.old-state) =? old-state ?=(%0 -.old-state)
%= old-state %= old-state
- %1 - %1
@ -79,16 +81,23 @@
^- [^content ? ?] ^- [^content ? ?]
[content public %.y] [content public %.y]
== ==
?> ?=(%3 -.old-state) =? cards ?=(%3 -.old-state)
[~ this(state old-state)] :_ cards
[%pass /serve-who %arvo %e %serve [~ /who] %home /gen/who/hoon ~]
=? old-state ?=(%3 -.old-state)
old-state(- %4)
?> ?=(%4 -.old-state)
[cards this(state old-state)]
:: ::
+$ serving-0 (map url-base=path [=clay=path public=?]) +$ serving-0 (map url-base=path [=clay=path public=?])
+$ serving-1 (map url-base=path [=content public=?]) +$ serving-1 (map url-base=path [=content public=?])
+$ serving-3 (map url-base=path [=content public=? single-page=?])
+$ versioned-state +$ versioned-state
$% state-0 $% state-0
[%1 state-1] [%1 state-1]
[%2 state-1] [%2 state-1]
state-3 state-3
state-4
== ==
:: ::
+$ state-0 +$ state-0
@ -100,6 +109,11 @@
$: =configuration:srv $: =configuration:srv
serving=serving-1 serving=serving-1
== ==
+$ state-3
$: %3
=configuration:srv
serving=serving-3
==
-- --
:: ::
++ on-poke ++ on-poke
@ -205,30 +219,35 @@
?~ ext.req-line site.req-line ?~ ext.req-line site.req-line
(snoc site.req-line u.ext.req-line) (snoc site.req-line u.ext.req-line)
=/ content=(unit [=content suffix=path public=?]) =/ content=(unit [=content suffix=path public=?])
(get-content pax is-file) (match-content-path pax is-file)
?~ content [not-found:gen %.n] ?~ content [not-found:gen %.n]
?- -.content.u.content ?- -.content.u.content
%clay %clay
=/ scry-path=path =/ scry-start=path
:* (scot %p our.bowl) :* (scot %p our.bowl)
q.byk.bowl q.byk.bowl
(scot %da now.bowl) (scot %da now.bowl)
(lowercase (weld path.content.u.content suffix.u.content)) path.content.u.content
== ==
=/ scry-path=path
(weld scry-start (lowercase suffix.u.content))
=? scry-path !.^(? %cu scry-path)
(weld scry-start /index/html)
?. .^(? %cu scry-path) [not-found:gen %.n] ?. .^(? %cu scry-path) [not-found:gen %.n]
?: ?=([~ %woff2] ext.req-line) ?: ?=([~ %woff2] ext.req-line)
:_ public.u.content :_ public.u.content
[[200 [['content-type' '/font/woff2'] ~]] `.^(octs %cx scry-path)] [[200 [['content-type' '/font/woff2'] ~]] `.^(octs %cx scry-path)]
=/ file (as-octs:mimes:html .^(@ %cx scry-path)) =/ file (as-octs:mimes:html .^(@ %cx scry-path))
:_ public.u.content :_ public.u.content
?+ ext.req-line not-found:gen =/ ext (rear scry-path)
[~ %js] (js-response:gen file) ?+ ext not-found:gen
[~ %css] (css-response:gen file) %js (js-response:gen file)
[~ %png] (png-response:gen file) %css (css-response:gen file)
[~ %svg] (svg-response:gen file) %png (png-response:gen file)
[~ %ico] (ico-response:gen file) %svg (svg-response:gen file)
%ico (ico-response:gen file)
:: ::
[~ %html] %html
%. file %. file
%* . html-response:gen %* . html-response:gen
cache cache
@ -262,17 +281,8 @@
char char
(add char ^~((sub 'a' 'A'))) (add char ^~((sub 'a' 'A')))
:: ::
++ get-content
|= [pax=path is-file=?]
^- (unit [content path ?])
=/ first-try (match-content-path pax (~(del by serving) /) is-file)
?^ first-try first-try
=/ root (~(get by serving) /)
?~ root ~
(match-content-path pax (~(gas by *^serving) [[/ u.root] ~]) is-file)
::
++ match-content-path ++ match-content-path
|= [pax=path =^serving is-file=?] |= [pax=path is-file=?]
^- (unit [content path ?]) ^- (unit [content path ?])
%+ roll %+ roll
%+ sort ~(tap by serving) %+ sort ~(tap by serving)
@ -338,6 +348,13 @@
[%x %clay %base %hash ~] [%x %clay %base %hash ~]
=/ versions (base-hash:version [our now]:bowl) =/ versions (base-hash:version [our now]:bowl)
``hash+!>(?~(versions 0v0 (end [0 25] i.versions))) ``hash+!>(?~(versions 0v0 (end [0 25] i.versions)))
::
[%x %our ~]
``json+!>(s+(scot %p our.bowl))
::
[%x %url *]
=/ url t.t.path
``noun+!>((~(has by serving) url))
== ==
++ on-agent on-agent:def ++ on-agent on-agent:def
++ on-fail on-fail:def ++ on-fail on-fail:def

View File

@ -5,8 +5,8 @@
/- glob, *resource /- glob, *resource
/+ default-agent, verb, dbug /+ default-agent, verb, dbug
|% |%
++ landscape-hash 0v4.3us6c.ma3il.h5bch.qacg3.70qjl ++ landscape-hash 0vrbiqe.v6al2.0b4jc.u9vp7.k1e0i
++ btc-wallet-hash 0v1.9p61c.bd4vn.deevh.0ldbq.fkqo3 ++ btc-wallet-hash 0v7.v4dng.o33qi.kc497.5jc02.ke5es
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))] +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ state-1 [%1 =globs:glob] +$ state-1 [%1 =globs:glob]
+$ all-states +$ all-states

View File

@ -26,18 +26,15 @@
state-one state-one
== ==
:: ::
+$ cached-transform +$ post-transform
$- indexed-post:store
$-([index:store post:store atom ?] [index:store post:store]) $-([index:store post:store atom ?] [index:store post:store])
:: ::
+$ cached-permission +$ post-to-permission
$-(indexed-post:store $-(vip-metadata:metadata permissions:store)) $-(indexed-post:store $-(vip-metadata:metadata permissions:store))
:: ::
:: TODO: come back to this and potentially use send a %t
:: to be notified of validator changes
+$ cache +$ cache
$: graph-to-mark=(map resource:res (unit mark)) $: graph-to-mark=(map resource:res (unit mark))
perm-marks=(map [mark @tas] cached-permission)
transform-marks=(map mark cached-transform)
== ==
:: ::
+$ inflated-state +$ inflated-state
@ -47,8 +44,6 @@
:: ::
+$ cache-action +$ cache-action
$% [%graph-to-mark (pair resource:res (unit mark))] $% [%graph-to-mark (pair resource:res (unit mark))]
[%perm-marks (pair (pair mark @tas) cached-permission)]
[%transform-marks (pair mark cached-transform)]
== ==
-- --
:: ::
@ -90,13 +85,9 @@
=/ a=cache-action !<(cache-action vase) =/ a=cache-action !<(cache-action vase)
=* c +.state =* c +.state
=* graph-to-mark graph-to-mark.c =* graph-to-mark graph-to-mark.c
=* perm-marks perm-marks.c
=* transform-marks transform-marks.c
=. c =. c
?- -.a ?- -.a
%graph-to-mark c(graph-to-mark (~(put by graph-to-mark) p.a q.a)) %graph-to-mark c(graph-to-mark (~(put by graph-to-mark) p.a q.a))
%perm-marks c(perm-marks (~(put by perm-marks) p.a q.a))
%transform-marks c(transform-marks (~(put by transform-marks) p.a q.a))
== ==
[~ this(+.state c)] [~ this(+.state c)]
:: ::
@ -142,12 +133,9 @@
|% |%
++ $ ++ $
^- (quip card (unit vase)) ^- (quip card (unit vase))
=/ transform=cached-transform =/ transform
%+ fall %. *indexed-post:store
(~(get by transform-marks) u.mark) .^(post-transform (scry:hc %cf %home /[u.mark]/transform-add-nodes))
=/ =tube:clay
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes))
!<(cached-transform (tube !>(*indexed-post:store)))
=/ [* result=(list [index:store node:store])] =/ [* result=(list [index:store node:store])]
%+ roll %+ roll
(flatten-node-map ~(tap by nodes.q.update)) (flatten-node-map ~(tap by nodes.q.update))
@ -166,13 +154,6 @@
%+ poke-self:pass:io %graph-cache-hook %+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action !> ^- cache-action
[%graph-to-mark rid mark] [%graph-to-mark rid mark]
::
?: (~(has by transform-marks) u.mark)
~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%transform-marks u.mark transform]
== ==
:: ::
++ flatten-node-map ++ flatten-node-map
@ -322,9 +303,7 @@
[[%no %no %no] ~] [[%no %no %no] ~]
=/ key [u.mark (perm-mark-name perm)] =/ key [u.mark (perm-mark-name perm)]
=/ convert =/ convert
%+ fall .^(post-to-permission (scry %cf %home /[u.mark]/(perm-mark-name perm)))
(~(get by perm-marks.cache) key)
.^(cached-permission (scry %cf %home /[u.mark]/(perm-mark-name perm)))
:- ((convert indexed-post) vip) :- ((convert indexed-post) vip)
%- zing %- zing
:~ ?: (~(has by graph-to-mark.cache) resource) :~ ?: (~(has by graph-to-mark.cache) resource)
@ -333,12 +312,6 @@
%+ poke-self:pass:io %graph-cache-hook %+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action !> ^- cache-action
[%graph-to-mark resource mark] [%graph-to-mark resource mark]
::
?: (~(has by perm-marks.cache) key) ~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%perm-marks [u.mark (perm-mark-name perm)] convert]
== ==
:: ::
++ perm-mark-name ++ perm-mark-name

View File

@ -16,20 +16,9 @@
+$ state-5 [%5 network:store] +$ state-5 [%5 network:store]
++ orm orm:store ++ orm orm:store
++ orm-log orm-log:store ++ orm-log orm-log:store
::
+$ cache
$: validators=(map mark $-(indexed-post:store indexed-post:store))
==
::
:: TODO: come back to this and potentially use ford runes or otherwise
:: send a %t to be notified of validator changes
+$ inflated-state
$: state-5
cache
==
-- --
:: ::
=| inflated-state =| state-5
=* state - =* state -
:: ::
%- agent:dbug %- agent:dbug
@ -41,7 +30,7 @@
def ~(. (default-agent this %|) bowl) def ~(. (default-agent this %|) bowl)
:: ::
++ on-init [~ this] ++ on-init [~ this]
++ on-save !>(-.state) ++ on-save !>(state)
++ on-load ++ on-load
|= =old=vase |= =old=vase
^- (quip card _this) ^- (quip card _this)
@ -91,7 +80,7 @@
(gas:orm-log ~ [now.bowl logged-update] ~) (gas:orm-log ~ [now.bowl logged-update] ~)
== ==
:: ::
%5 [cards this(-.state old, +.state *cache)] %5 [cards this(state old)]
== ==
:: ::
++ on-watch ++ on-watch
@ -593,8 +582,6 @@
?~ mark ?~ mark
[%.y state] [%.y state]
=/ validate=$-(indexed-post:store indexed-post:store) =/ validate=$-(indexed-post:store indexed-post:store)
%+ fall
(~(get by validators) u.mark)
.^ $-(indexed-post:store indexed-post:store) .^ $-(indexed-post:store indexed-post:store)
%cf %cf
(scot %p our.bowl) (scot %p our.bowl)
@ -604,8 +591,6 @@
%graph-indexed-post %graph-indexed-post
~ ~
== ==
=? validators !(~(has by validators) u.mark)
(~(put by validators) u.mark validate)
:_ state :_ state
|- ^- ? |- ^- ?
?~ graph %.y ?~ graph %.y
@ -624,7 +609,7 @@
++ poke-import ++ poke-import
|= arc=* |= arc=*
^- (quip card _state) ^- (quip card _state)
=^ cards -.state =^ cards state
(import:store arc our.bowl) (import:store arc our.bowl)
[cards state] [cards state]
-- --

View File

@ -127,8 +127,13 @@
++ hide ++ hide
|= rid=resource |= rid=resource
^- (quip card _state) ^- (quip card _state)
=/ =request:view (~(got by joining) rid)
?: ?=(final:view progress.request)
=. joining (~(del by joining) rid)
:_ state
(fact:io group-view-update+!>(`update:view`[%initial joining]) /all ~)^~
:- (fact:io group-view-update+!>([%hide rid]) /all ~)^~ :- (fact:io group-view-update+!>([%hide rid]) /all ~)^~
state(joining (~(jab by joining) rid |=(request:view +<(hidden %.y)))) state(joining (~(put by joining) rid request(hidden %.y)))
:: ::
++ has-joined ++ has-joined
|= rid=resource |= rid=resource
@ -160,7 +165,7 @@
++ tx-progress ++ tx-progress
|= =progress:view |= =progress:view
=. joining =. joining
(~(jab by joining) rid |=(request:view +<(progress progress))) (~(jab by joining) rid |=(req=request:view req(progress progress)))
=; =cage =; =cage
(emit (fact:io cage /all tx+(en-path:resource rid) ~)) (emit (fact:io cage /all tx+(en-path:resource rid) ~))
group-view-update+!>([%progress rid progress]) group-view-update+!>([%progress rid progress])
@ -217,10 +222,11 @@
?> ?=(%poke-ack -.sign) ?> ?=(%poke-ack -.sign)
?^ p.sign ?^ p.sign
(cleanup %no-perms) (cleanup %no-perms)
=> %- emit =. jn-core
(tx-progress %added)
%- emit
%+ poke-our:(jn-pass-io /pull-groups) %group-pull-hook %+ poke-our:(jn-pass-io /pull-groups) %group-pull-hook
pull-hook-action+!>([%add ship rid]) pull-hook-action+!>([%add ship rid])
(tx-progress %added)
:: ::
%pull-groups %pull-groups
?> ?=(%poke-ack -.sign) ?> ?=(%poke-ack -.sign)
@ -324,7 +330,6 @@
|= =progress:view |= =progress:view
=. jn-core =. jn-core
(tx-progress progress) (tx-progress progress)
=. joining (~(del by joining) rid)
=. jn-core =. jn-core
(emit (leave-our:(jn-pass-io /groups) %group-store)) (emit (leave-our:(jn-pass-io /groups) %group-store))
(emit (leave-our:(jn-pass-io /md) %metadata-store)) (emit (leave-our:(jn-pass-io /md) %metadata-store))

View File

@ -74,21 +74,9 @@
== ==
:_ this(state old) :_ this(state old)
=. cards (flop cards) =. cards (flop cards)
%+ welp
?: (~(has by wex.bowl) [/graph our.bowl %graph-store]) ?: (~(has by wex.bowl) [/graph our.bowl %graph-store])
cards cards
[watch-graph:ha cards] [watch-graph:ha cards]
%+ turn
^- (list mark)
:~ %graph-validator-chat
%graph-validator-link
%graph-validator-publish
==
|= =mark
^- card
=/ =wire /validator/[mark]
=/ =rave:clay [%sing %f [%da now.bowl] /[mark]/notification-kind]
[%pass wire %arvo %c %warp our.bowl [%home `rave]]
:: ::
++ on-watch ++ on-watch
|= =path |= =path
@ -281,11 +269,8 @@
^- (quip card _this) ^- (quip card _this)
?+ wire (on-arvo:def wire sign-arvo) ?+ wire (on-arvo:def wire sign-arvo)
:: ::
[%validator @ ~] :: no longer necessary
:_ this [%validator @ ~] [~ this]
=* validator i.t.wire
=/ =rave:clay [%next %f [%da now.bowl] /[validator]/notification-kind]
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
== ==
++ on-fail on-fail:def ++ on-fail on-fail:def
-- --

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div> <div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script> <script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script> <script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.af45eeaf465dff7d73f1.js"></script> <script src="/~landscape/js/bundle/index.8074ae0006fba19803f5.js"></script>
</body> </body>
</html> </html>

View File

@ -107,6 +107,7 @@
+$ state-9 [%9 base-state-3] +$ state-9 [%9 base-state-3]
+$ state-10 [%10 base-state-3] +$ state-10 [%10 base-state-3]
+$ state-11 [%11 base-state-3] +$ state-11 [%11 base-state-3]
+$ state-12 [%12 base-state-3]
+$ versioned-state +$ versioned-state
$% state-0 $% state-0
state-1 state-1
@ -120,10 +121,11 @@
state-9 state-9
state-10 state-10
state-11 state-11
state-12
== ==
:: ::
+$ inflated-state +$ inflated-state
$: state-11 $: state-12
cached-indices cached-indices
== ==
-- --
@ -243,7 +245,7 @@
=| cards=(list card) =| cards=(list card)
|^ |^
=* loop $ =* loop $
?: ?=(%11 -.old) ?: ?=(%12 -.old)
:- cards :- cards
%_ state %_ state
associations associations.old associations associations.old
@ -251,6 +253,8 @@
group-indices (rebuild-group-indices associations.old) group-indices (rebuild-group-indices associations.old)
app-indices (rebuild-app-indices associations.old) app-indices (rebuild-app-indices associations.old)
== ==
?: ?=(%11 -.old)
$(-.old %12, associations.old (reset-group-hidden associations.old))
?: ?=(%10 -.old) ?: ?=(%10 -.old)
$(-.old %11, associations.old (hide-dm-assoc associations.old)) $(-.old %11, associations.old (hide-dm-assoc associations.old))
?: ?=(%9 -.old) ?: ?=(%9 -.old)
@ -293,6 +297,17 @@
:: pre-breach, can safely throw away :: pre-breach, can safely throw away
loop(old *state-8) loop(old *state-8)
:: ::
++ reset-group-hidden
|= assoc=associations:store
^- associations:store
%- ~(gas by *associations:store)
%+ turn ~(tap by assoc)
|= [m=md-resource:store [g=resource met=metadatum:store]]
^- [md-resource:store association:store]
=? hidden.met ?=(%groups app-name.m)
%.n
[m [g met]]
::
++ hide-dm-assoc ++ hide-dm-assoc
|= assoc=associations:store |= assoc=associations:store
^- associations:store ^- associations:store

View File

@ -8,6 +8,12 @@
:: ::
|% |%
+$ card card:agent:gall +$ card card:agent:gall
+$ state-0
$: observers=(map serial observer:sur)
warm-cache=_|
static-conversions=(set [term term])
==
::
+$ versioned-state +$ versioned-state
$% [%0 observers=(map serial observer:sur)] $% [%0 observers=(map serial observer:sur)]
[%1 observers=(map serial observer:sur)] [%1 observers=(map serial observer:sur)]
@ -15,6 +21,7 @@
[%3 observers=(map serial observer:sur)] [%3 observers=(map serial observer:sur)]
[%4 observers=(map serial observer:sur)] [%4 observers=(map serial observer:sur)]
[%5 observers=(map serial observer:sur) warm-cache=_|] [%5 observers=(map serial observer:sur) warm-cache=_|]
[%6 state-0]
== ==
:: ::
+$ serial @uv +$ serial @uv
@ -28,7 +35,7 @@
-- --
:: ::
%- agent:dbug %- agent:dbug
=| [%5 observers=(map serial observer:sur) warm-cache=_|] =| [%6 state-0]
=* state - =* state -
:: ::
^- agent:gall ^- agent:gall
@ -44,6 +51,33 @@
(act [%watch %group-store /groups %group-on-remove-member]) (act [%watch %group-store /groups %group-on-remove-member])
(act [%watch %metadata-store /updates %md-on-add-group-feed]) (act [%watch %metadata-store /updates %md-on-add-group-feed])
(act [%warm-cache-all ~]) (act [%warm-cache-all ~])
::
(warm-static %graph-validator-chat %graph-indexed-post)
(warm-static %graph-validator-publish %graph-indexed-post)
(warm-static %graph-validator-link %graph-indexed-post)
(warm-static %graph-validator-post %graph-indexed-post)
(warm-static %graph-validator-dm %graph-indexed-post)
::
(warm-static %graph-validator-chat %graph-permissions-add)
(warm-static %graph-validator-publish %graph-permissions-add)
(warm-static %graph-validator-link %graph-permissions-add)
(warm-static %graph-validator-post %graph-permissions-add)
::
(warm-static %graph-validator-chat %graph-permissions-remove)
(warm-static %graph-validator-publish %graph-permissions-remove)
(warm-static %graph-validator-link %graph-permissions-remove)
(warm-static %graph-validator-post %graph-permissions-remove)
::
(warm-static %graph-validator-chat %notification-kind)
(warm-static %graph-validator-publish %notification-kind)
(warm-static %graph-validator-link %notification-kind)
(warm-static %graph-validator-post %notification-kind)
(warm-static %graph-validator-dm %notification-kind)
::
(warm-static %graph-validator-chat %transform-add-nodes)
(warm-static %graph-validator-publish %transform-add-nodes)
(warm-static %graph-validator-link %transform-add-nodes)
(warm-static %graph-validator-post %transform-add-nodes)
== ==
:: ::
++ act ++ act
@ -57,6 +91,19 @@
%observe-action %observe-action
!>(action) !>(action)
== ==
::
++ warm-static
|= [from=term to=term]
^- card
:* %pass
/poke
%agent
[our.bowl %observe-hook]
%poke
%observe-action
!> ^- action:sur
[%warm-static-conversion from to]
==
-- --
:: ::
++ on-save !>(state) ++ on-save !>(state)
@ -68,8 +115,41 @@
=| cards=(list card) =| cards=(list card)
|- |-
?- -.old-state ?- -.old-state
%5 %6
[cards this(state old-state)] [cards this(state old-state)]
::
%5
=. cards
%+ weld cards
:~ (warm-static %graph-validator-chat %graph-indexed-post)
(warm-static %graph-validator-publish %graph-indexed-post)
(warm-static %graph-validator-link %graph-indexed-post)
(warm-static %graph-validator-post %graph-indexed-post)
(warm-static %graph-validator-dm %graph-indexed-post)
::
(warm-static %graph-validator-chat %graph-permissions-add)
(warm-static %graph-validator-publish %graph-permissions-add)
(warm-static %graph-validator-link %graph-permissions-add)
(warm-static %graph-validator-post %graph-permissions-add)
::
(warm-static %graph-validator-chat %graph-permissions-remove)
(warm-static %graph-validator-publish %graph-permissions-remove)
(warm-static %graph-validator-link %graph-permissions-remove)
(warm-static %graph-validator-post %graph-permissions-remove)
::
(warm-static %graph-validator-chat %notification-kind)
(warm-static %graph-validator-publish %notification-kind)
(warm-static %graph-validator-link %notification-kind)
(warm-static %graph-validator-post %notification-kind)
(warm-static %graph-validator-dm %notification-kind)
::
(warm-static %graph-validator-chat %transform-add-nodes)
(warm-static %graph-validator-publish %transform-add-nodes)
(warm-static %graph-validator-link %transform-add-nodes)
(warm-static %graph-validator-post %transform-add-nodes)
==
$(old-state [%6 observers.old-state %.n ~])
::
%4 %4
=. cards =. cards
:_ cards :_ cards
@ -109,6 +189,19 @@
%observe-action %observe-action
!>(action) !>(action)
== ==
::
++ warm-static
|= [from=term to=term]
^- card
:* %pass
/poke
%agent
[our.bowl %observe-hook]
%poke
%observe-action
!> ^- action:sur
[%warm-static-conversion from to]
==
-- --
:: ::
++ on-poke ++ on-poke
@ -126,6 +219,8 @@
%ignore (ignore observer vals) %ignore (ignore observer vals)
%warm-cache-all warm-cache-all %warm-cache-all warm-cache-all
%cool-cache-all cool-cache-all %cool-cache-all cool-cache-all
%warm-static-conversion (warm-static-conversion from.action to.action)
%cool-static-conversion (cool-static-conversion from.action to.action)
== ==
:: ::
++ watch ++ watch
@ -170,6 +265,23 @@
?. warm-cache ?. warm-cache
~|('cannot cool down cache that is already cool' !!) ~|('cannot cool down cache that is already cool' !!)
[~ this(warm-cache %.n)] [~ this(warm-cache %.n)]
::
++ warm-static-conversion
|= [from=term to=term]
^- (quip card _this)
?: (~(has in static-conversions) [from to])
~|('cannot warm up a static conversion that is already warm' !!)
:_ this(static-conversions (~(put in static-conversions) [from to]))
=/ =wire /static-convert/[from]/[to]
=/ =rave:clay [%sing %f [%da now.bowl] /[from]/[to]]
[%pass wire %arvo %c %warp our.bowl %home `rave]~
::
++ cool-static-conversion
|= [from=term to=term]
^- (quip card _this)
?. (~(has in static-conversions) [from to])
~|('cannot cool a static conversion that is already cool' !!)
[~ this(static-conversions (~(del in static-conversions) [from to]))]
-- --
:: ::
++ on-agent ++ on-agent
@ -326,6 +438,18 @@
~ ~
=/ =rave:clay [%next %b q.p.u.riot mark] =/ =rave:clay [%next %b q.p.u.riot mark]
[%pass wire %arvo %c %warp our.bowl %home `rave]~ [%pass wire %arvo %c %warp our.bowl %home `rave]~
::
[%static-convert @ @ ~]
=* from i.t.wire
=* to i.t.t.wire
?. (~(has in static-conversions) [from to])
~
?> ?=([%clay %writ *] sign-arvo)
=* riot p.sign-arvo
?~ riot
~
=/ =rave:clay [%next %f q.p.u.riot /[from]/[to]]
[%pass wire %arvo %c %warp our.bowl %home `rave]~
== ==
:: ::
++ on-watch on-watch:def ++ on-watch on-watch:def

View File

@ -0,0 +1,11 @@
:: group-view|join: Join a group
::
/- view=group-view
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[him=ship name=term ~] ~]
==
::
:- %group-view-action
^- action:view
[%join [him name] him]

View File

@ -1,5 +1,6 @@
/- bc=bitcoin /- bc=bitcoin
/+ bcu=bitcoin-utils /+ bcu=bitcoin-utils
~% %bip-158-top ..part ~
|% |%
++ params ++ params
|% |%
@ -8,6 +9,7 @@
-- --
:: ::
++ siphash ++ siphash
~/ %siphash
|= [k=byts m=byts] |= [k=byts m=byts]
^- byts ^- byts
|^ |^
@ -86,25 +88,28 @@
:: write appends to the back :: write appends to the back
:: ::
++ str ++ str
~% %str ..params ~
|% |%
++ read-bit ++ read-bit
~/ %read-bit
|= s=bits:bc |= s=bits:bc
^- [bit=@ub rest=bits:bc] ^- [bit=@ub rest=bits:bc]
?> (gth wid.s 0) ?> (gth wid.s 0)
:* ?:((gth wid.s (met 0 dat.s)) 0b0 0b1) :+ ?:((gth wid.s (met 0 dat.s)) 0b0 0b1)
[(dec wid.s) (end [0 (dec wid.s)] dat.s)] (dec wid.s)
== (end [0 (dec wid.s)] dat.s)
::
:: ::
++ read-bits ++ read-bits
~/ %read-bits
|= [n=@ s=bits:bc] |= [n=@ s=bits:bc]
^- [bits:bc rest=bits:bc] ^- [bits:bc rest=bits:bc]
=| bs=bits:bc =/ r=@ (sub wid.s n)
|- :- n^(cut 0 [r n] dat.s)
?: =(n 0) [bs s] r^(cut 0 [0 r] dat.s)
=^ b s (read-bit s)
$(n (dec n), bs (write-bits bs [1 b]))
:: ::
++ write-bits ++ write-bits
~/ %write-bits
|= [s1=bits:bc s2=bits:bc] |= [s1=bits:bc s2=bits:bc]
^- bits:bc ^- bits:bc
[(add wid.s1 wid.s2) (can 0 ~[s2 s1])] [(add wid.s1 wid.s2) (can 0 ~[s2 s1])]
@ -112,6 +117,7 @@
:: +gol: Golomb-Rice encoding/decoding :: +gol: Golomb-Rice encoding/decoding
:: ::
++ gol ++ gol
~% %gol ..params ~
|% |%
:: +en: encode x and append to end of s :: +en: encode x and append to end of s
:: - s: bits stream :: - s: bits stream
@ -119,6 +125,7 @@
:: - p: golomb-rice p param :: - p: golomb-rice p param
:: ::
++ en ++ en
~/ %en
|= [s=bits:bc x=@ p=@] |= [s=bits:bc x=@ p=@]
^- bits:bc ^- bits:bc
=+ q=(rsh [0 p] x) =+ q=(rsh [0 p] x)
@ -128,6 +135,7 @@
(write-bits:str unary r) (write-bits:str unary r)
:: ::
++ de ++ de
~/ %de
|= [s=bits:bc p=@] |= [s=bits:bc p=@]
^- [delta=@ rest=bits:bc] ^- [delta=@ rest=bits:bc]
|^ ?> (gth wid.s 0) |^ ?> (gth wid.s 0)
@ -148,6 +156,7 @@
:: +hsh :: +hsh
:: ::
++ hsh ++ hsh
~% %hsh ..params ~
|% |%
:: +to-range :: +to-range
:: - item: scriptpubkey to hash :: - item: scriptpubkey to hash
@ -155,9 +164,10 @@
:: - k: key for siphash (end of blockhash, reversed) :: - k: key for siphash (end of blockhash, reversed)
:: ::
++ to-range ++ to-range
~/ %to-range
|= [item=byts f=@ k=byts] |= [item=byts f=@ k=byts]
^- @ ^- @
(rsh [0 64] (mul f (swp 3 dat:(siphash k item)))) (rsh [0 64] (mul f (rev 3 (siphash k item))))
:: +set-construct: return sorted hashes of scriptpubkeys :: +set-construct: return sorted hashes of scriptpubkeys
:: ::
++ set-construct ++ set-construct
@ -171,6 +181,7 @@
-- --
:: ::
++ parse-filter ++ parse-filter
~/ %parse-filter
|= filter=hexb:bc |= filter=hexb:bc
^- [n=@ux gcs-set=bits:bc] ^- [n=@ux gcs-set=bits:bc]
=/ n n:(de:csiz:bcu filter) =/ n n:(de:csiz:bcu filter)
@ -180,6 +191,7 @@
:: +to-key: blockhash (little endian) to key for siphash :: +to-key: blockhash (little endian) to key for siphash
:: ::
++ to-key ++ to-key
~/ %to-key
|= blockhash=tape |= blockhash=tape
^- byts ^- byts
%+ take:byt:bcu 16 %+ take:byt:bcu 16
@ -191,6 +203,7 @@
:: - targets: scriptpubkeys to match :: - targets: scriptpubkeys to match
:: ::
++ match ++ match
~/ %match
|= [filter=hexb:bc k=byts targets=(list byts)] |= [filter=hexb:bc k=byts targets=(list byts)]
^- ? ^- ?
=/ [p=@ m=@] [p:params m:params] =/ [p=@ m=@] [p:params m:params]
@ -214,6 +227,7 @@
:: - targets: scriptpubkeys to match :: - targets: scriptpubkeys to match
:: ::
++ all-match ++ all-match
~/ %all-match
|= [filter=hexb:bc blockhash=hexb:bc targets=(list [address:bc byts])] |= [filter=hexb:bc blockhash=hexb:bc targets=(list [address:bc byts])]
^- (set [address:bc hexb:bc]) ^- (set [address:bc hexb:bc])
=/ k (to-key (trip (to-cord:hxb:bcu blockhash))) =/ k (to-key (trip (to-cord:hxb:bcu blockhash)))

View File

@ -110,6 +110,7 @@
%cancel-tx (hexb txid.upd) %cancel-tx (hexb txid.upd)
%new-address (address address.upd) %new-address (address address.upd)
%balance (balance balance.upd) %balance (balance balance.upd)
%scan-progress (scan-progress main.upd change.upd)
%error s+error.upd %error s+error.upd
%broadcast-success ~ %broadcast-success ~
== ==
@ -161,6 +162,19 @@
unconfirmed+(numb q.u.b) unconfirmed+(numb q.u.b)
== ==
:: ::
++ scan-progress
|= [main=(unit idx:bitcoin) change=(unit idx:bitcoin)]
|^ ^- json
%- pairs
:~ main+(from-unit main)
change+(from-unit change)
==
++ from-unit
|= i=(unit idx:bitcoin)
?~ i ~
(numb u.i)
--
::
++ btc-state ++ btc-state
|= bs=btc-state:btc-wallet |= bs=btc-state:btc-wallet
^- json ^- json

View File

@ -1,8 +1,8 @@
:: lib/bitcoin-utils.hoon :: lib/bitcoin-utils.hoon
:: Utilities for working with BTC data types and transactions :: Utilities for working with BTC data types and transactions
:: ::
/- sur=bitcoin /- *bitcoin
=, sur ~% %bitcoin-utils-lib ..part ~
|% |%
:: ::
:: TODO: move this bit/byt stuff to zuse :: TODO: move this bit/byt stuff to zuse
@ -12,6 +12,7 @@
:: +blop: munge bit and byt sequences (cat, flip, take, drop) :: +blop: munge bit and byt sequences (cat, flip, take, drop)
:: ::
++ blop ++ blop
~/ %blop
|_ =bloq |_ =bloq
+$ biyts [wid=@ud dat=@] +$ biyts [wid=@ud dat=@]
++ cat ++ cat
@ -48,6 +49,7 @@
++ byt ~(. blop 3) ++ byt ~(. blop 3)
:: ::
++ bit ++ bit
~/ %bit
=/ bl ~(. blop 0) =/ bl ~(. blop 0)
|% |%
++ cat cat:bl:bit ++ cat cat:bl:bit
@ -79,16 +81,19 @@
:: big endian sha256: input and output are both MSB first (big endian) :: big endian sha256: input and output are both MSB first (big endian)
:: ::
++ sha256 ++ sha256
~/ %sha256
|= =byts |= =byts
^- hexb ^- hexb
%- flip:byt %- flip:byt
[32 (shay (flip:byt byts))] [32 (shay (flip:byt byts))]
:: ::
++ dsha256 ++ dsha256
~/ %dsha256
|= =byts |= =byts
(sha256 (sha256 byts)) (sha256 (sha256 byts))
:: ::
++ hash-160 ++ hash-160
~/ %hash-160
|= val=byts |= val=byts
^- hexb ^- hexb
=, ripemd:crypto =, ripemd:crypto
@ -100,8 +105,10 @@
:: hxb: hex parsing utilities :: hxb: hex parsing utilities
:: ::
++ hxb ++ hxb
~% %hxb ..blop ~
|% |%
++ from-cord ++ from-cord
~/ %from-cord
|= h=@t |= h=@t
^- hexb ^- hexb
?: =('' h) 1^0x0 ?: =('' h) 1^0x0
@ -113,10 +120,11 @@
=+ (rsh [3 2] -) =+ (rsh [3 2] -)
:: Parse hex to atom :: Parse hex to atom
:: ::
:- (div (lent (trip h)) 2) =/ a (need (de:base16:mimes:html -))
`@ux`(rash - hex) [-.a `@ux`+.a]
:: ::
++ to-cord ++ to-cord
~/ %to-cord
|= =hexb |= =hexb
^- cord ^- cord
(en:base16:mimes:html hexb) (en:base16:mimes:html hexb)
@ -128,8 +136,10 @@
:: - decode: little endian to big endian :: - decode: little endian to big endian
:: ::
++ csiz ++ csiz
~% %csiz ..blop ~
|% |%
++ en ++ en
~/ %en
|= a=@ |= a=@
^- hexb ^- hexb
=/ l=@ (met 3 a) =/ l=@ (met 3 a)
@ -140,6 +150,7 @@
~|("Cannot encode CompactSize longer than 8 bytes" !!) ~|("Cannot encode CompactSize longer than 8 bytes" !!)
:: ::
++ de ++ de
~/ %de
|= h=hexb |= h=hexb
^- [n=hexb rest=hexb] ^- [n=hexb rest=hexb]
=/ s=@ux dat:(take:byt 1 h) =/ s=@ux dat:(take:byt 1 h)
@ -162,5 +173,4 @@
=> (de h) => (de h)
[dat.n rest] [dat.n rest]
-- --
::
-- --

View File

@ -2,13 +2,13 @@
:: top-level Bitcoin constants :: top-level Bitcoin constants
:: expose BIP libraries :: expose BIP libraries
:: ::
/- sur=bitcoin /- *bitcoin
/+ bech32=bip-b173, pbt=bip-b174, bcu=bitcoin-utils, bip-b158 /+ bech32=bip-b173, pbt=bip-b174, bcu=bitcoin-utils, bip-b158
=, sur ~% %bitcoin-lib ..part ~
=, bcu
|% |%
++ overhead-weight ^-(vbytes 11) ++ overhead-weight ^-(vbytes 11)
++ input-weight ++ input-weight
~/ %input-weight
|= =bipt |= =bipt
^- vbytes ^- vbytes
?- bipt ?- bipt
@ -40,8 +40,10 @@
:: adr: address manipulation :: adr: address manipulation
:: ::
++ adr ++ adr
~% %adr ..overhead-weight ~
|% |%
++ get-bipt ++ get-bipt
~/ %get-bipt
|= a=address |= a=address
^- bipt ^- bipt
=/ spk=hexb (to-script-pubkey:adr a) =/ spk=hexb (to-script-pubkey:adr a)
@ -52,35 +54,39 @@
~|("Invalid address" !!) ~|("Invalid address" !!)
:: ::
++ to-cord ++ to-cord
~/ %to-cord
|= a=address ^- cord |= a=address ^- cord
?: ?=([%base58 *] a) ?: ?=([%base58 *] a)
(scot %uc +.a) %- crip
%+ slag 2
(scow %uc +.a)
+.a +.a
:: ::
++ from-pubkey ++ from-pubkey
~/ %from-pubkey
|= [=bipt =network pubkey=hexb] |= [=bipt =network pubkey=hexb]
^- address ^- address
?- bipt ?- bipt
%44 %44
:- %base58 :- %base58
=< ^-(@uc dat) =< ^-(@uc dat)
%- cat:byt %- cat:byt:bcu
:- ?- network :- ?- network
%main 1^0x0 %main 1^0x0
%testnet 1^0x6f %testnet 1^0x6f
== ==
~[(hash-160 pubkey)] ~[(hash-160:bcu pubkey)]
:: ::
%49 %49
:- %base58 :- %base58
=< ^-(@uc dat) =< ^-(@uc dat)
%- cat:byt %- cat:byt:bcu
:~ ?- network :~ ?- network
%main 1^0x5 %main 1^0x5
%testnet 1^0xc4 %testnet 1^0xc4
== ==
%- hash-160 %- hash-160:bcu
(cat:byt ~[2^0x14 (hash-160 pubkey)]) (cat:byt:bcu ~[2^0x14 (hash-160:bcu pubkey)])
== ==
:: ::
%84 %84
@ -89,6 +95,7 @@
== ==
:: ::
++ from-cord ++ from-cord
~/ %from-cord
|= addrc=@t |= addrc=@t
|^ |^
=/ addrt=tape (trip addrc) =/ addrt=tape (trip addrc)
@ -117,12 +124,13 @@
-- --
:: ::
++ to-script-pubkey ++ to-script-pubkey
~/ %to-script-pubkey
|= =address |= =address
^- hexb ^- hexb
?- -.address ?- -.address
%bech32 %bech32
=+ h=(from-address:bech32 +.address) =+ h=(from-address:bech32 +.address)
%- cat:byt %- cat:byt:bcu
:~ 1^0x0 :~ 1^0x0
1^wid.h 1^wid.h
h h
@ -130,21 +138,21 @@
:: ::
%base58 %base58
=/ h=hexb [21 `@ux`+.address] =/ h=hexb [21 `@ux`+.address]
=+ lead-byt=dat:(take:byt 1 h) =+ lead-byt=dat:(take:byt:bcu 1 h)
=/ version-network=[bipt network] =/ version-network=[bipt network]
?: =(0x0 lead-byt) [%44 %main] ?: =(0x0 lead-byt) [%44 %main]
?: =(0x6f lead-byt) [%44 %testnet] ?: =(0x6f lead-byt) [%44 %testnet]
?: =(0x5 lead-byt) [%49 %main] ?: =(0x5 lead-byt) [%49 %main]
?: =(0xc4 lead-byt) [%49 %testnet] ?: =(0xc4 lead-byt) [%49 %testnet]
~|("Invalid base58 address: {<+.address>}" !!) ~|("Invalid base58 address: {<+.address>}" !!)
%- cat:byt %- cat:byt:bcu
?: ?=(%44 -.version-network) ?: ?=(%44 -.version-network)
:~ 3^0x76.a914 :~ 3^0x76.a914
(drop:byt 1 h) (drop:byt:bcu 1 h)
2^0x88ac 2^0x88ac
== ==
:~ 2^0xa914 :~ 2^0xa914
(drop:byt 1 h) (drop:byt:bcu 1 h)
1^0x87 1^0x87
== ==
== ==
@ -155,6 +163,8 @@
:: - ignores signatures in inputs :: - ignores signatures in inputs
:: ::
++ txu ++ txu
~% %bitcoin-lib-txu ..overhead-weight ~
=, bcu
|% |%
++ en ++ en
|% |%

View File

@ -1,8 +1,6 @@
/- bp=btc-provider, json-rpc /- bp=btc-provider, json-rpc
/+ bc=bitcoin /+ bc=bitcoin, bcu=bitcoin-utils
^? ~% %btc-provider-lib ..part ~
::=< [sur .]
::=, sur
|% |%
:: +from-epoch: time since Jan 1, 1970 in seconds. :: +from-epoch: time since Jan 1, 1970 in seconds.
:: ::
@ -36,6 +34,7 @@
api-url.host-info ract api-url.host-info ract
:: ::
++ rpc ++ rpc
~/ %rpc
=, dejs:format =, dejs:format
|% |%
++ parse-result ++ parse-result
@ -62,6 +61,7 @@
%get-block-info %get-block-info
[id.res (block-info res.res)] [id.res (block-info res.res)]
== ==
::
++ address-info ++ address-info
%- ot %- ot
:~ [%address (cu from-cord:adr:bc so)] :~ [%address (cu from-cord:adr:bc so)]
@ -69,47 +69,53 @@
[%used bo] [%used bo]
[%block ni] [%block ni]
== ==
::
++ utxo ++ utxo
%- ot %- ot
:~ ['tx_pos' ni] :~ ['tx_pos' ni]
['tx_hash' (cu from-cord:hxb:bc so)] ['tx_hash' (cu from-cord:hxb:bcu so)]
[%height ni] [%height ni]
[%value ni] [%value ni]
[%recvd (cu from-epoch ni)] [%recvd (cu from-epoch ni)]
== ==
::
++ tx-vals ++ tx-vals
%- ot %- ot
:~ [%included bo] :~ [%included bo]
[%txid (cu from-cord:hxb:bc so)] [%txid (cu from-cord:hxb:bcu so)]
[%confs ni] [%confs ni]
[%recvd (cu from-epoch ni)] [%recvd (cu from-epoch ni)]
[%inputs (ar tx-val)] [%inputs (ar tx-val)]
[%outputs (ar tx-val)] [%outputs (ar tx-val)]
== ==
::
++ tx-val ++ tx-val
%- ot %- ot
:~ [%txid (cu from-cord:hxb:bc so)] :~ [%txid (cu from-cord:hxb:bcu so)]
[%pos ni] [%pos ni]
[%address (cu from-cord:adr:bc so)] [%address (cu from-cord:adr:bc so)]
[%value ni] [%value ni]
== ==
::
++ raw-tx ++ raw-tx
%- ot %- ot
:~ [%txid (cu from-cord:hxb:bc so)] :~ [%txid (cu from-cord:hxb:bcu so)]
[%rawtx (cu from-cord:hxb:bc so)] [%rawtx (cu from-cord:hxb:bcu so)]
== ==
::
++ broadcast-tx ++ broadcast-tx
%- ot %- ot
:~ [%txid (cu from-cord:hxb:bc so)] :~ [%txid (cu from-cord:hxb:bcu so)]
[%broadcast bo] [%broadcast bo]
[%included bo] [%included bo]
== ==
::
++ block-info ++ block-info
%- ot %- ot
:~ [%block ni] :~ [%block ni]
[%fee (mu ni)] [%fee (mu ni)]
[%blockhash (cu from-cord:hxb:bc so)] [%blockhash (cu from-cord:hxb:bcu so)]
[%blockfilter (cu from-cord:hxb:bc so)] [%blockfilter (cu from-cord:hxb:bcu so)]
== ==
-- --
-- --
@ -126,17 +132,17 @@
%get-tx-vals %get-tx-vals
%- get-request %- get-request
%+ mk-url '/gettxvals/' %+ mk-url '/gettxvals/'
(to-cord:hxb:bc txid.ract) (to-cord:hxb:bcu txid.ract)
:: ::
%get-raw-tx %get-raw-tx
%- get-request %- get-request
%+ mk-url '/getrawtx/' %+ mk-url '/getrawtx/'
(to-cord:hxb:bc txid.ract) (to-cord:hxb:bcu txid.ract)
:: ::
%broadcast-tx %broadcast-tx
%- get-request %- get-request
%+ mk-url '/broadcasttx/' %+ mk-url '/broadcasttx/'
(to-cord:hxb:bc rawtx.ract) (to-cord:hxb:bcu rawtx.ract)
:: ::
%get-block-count %get-block-count
%- get-request %- get-request

View File

@ -1,7 +1,7 @@
:: lib/btc.hoon :: lib/btc.hoon
:: ::
/- *btc-wallet, json-rpc, bp=btc-provider /- *btc-wallet, json-rpc, bp=btc-provider
/+ bip32, bc=bitcoin /+ bip32, bc=bitcoin, bcu=bitcoin-utils
=, secp:crypto =, secp:crypto
=+ ecc=secp256k1 =+ ecc=secp256k1
|% |%
@ -424,6 +424,7 @@
%get-block-info %get-block-info
[id.res (block-info res.res)] [id.res (block-info res.res)]
== ==
::
++ address-info ++ address-info
%- ot %- ot
:~ [%address (cu from-cord:adr:bc so)] :~ [%address (cu from-cord:adr:bc so)]
@ -434,7 +435,7 @@
++ utxo ++ utxo
%- ot %- ot
:~ ['tx_pos' ni] :~ ['tx_pos' ni]
['tx_hash' (cu from-cord:hxb:bc so)] ['tx_hash' (cu from-cord:hxb:bcu so)]
[%height ni] [%height ni]
[%value ni] [%value ni]
[%recvd (cu from-epoch ni)] [%recvd (cu from-epoch ni)]
@ -442,7 +443,7 @@
++ tx-vals ++ tx-vals
%- ot %- ot
:~ [%included bo] :~ [%included bo]
[%txid (cu from-cord:hxb:bc so)] [%txid (cu from-cord:hxb:bcu so)]
[%confs ni] [%confs ni]
[%recvd (cu from-epoch ni)] [%recvd (cu from-epoch ni)]
[%inputs (ar tx-val)] [%inputs (ar tx-val)]
@ -450,19 +451,19 @@
== ==
++ tx-val ++ tx-val
%- ot %- ot
:~ [%txid (cu from-cord:hxb:bc so)] :~ [%txid (cu from-cord:hxb:bcu so)]
[%pos ni] [%pos ni]
[%address (cu from-cord:adr:bc so)] [%address (cu from-cord:adr:bc so)]
[%value ni] [%value ni]
== ==
++ raw-tx ++ raw-tx
%- ot %- ot
:~ [%txid (cu from-cord:hxb:bc so)] :~ [%txid (cu from-cord:hxb:bcu so)]
[%rawtx (cu from-cord:hxb:bc so)] [%rawtx (cu from-cord:hxb:bcu so)]
== ==
++ broadcast-tx ++ broadcast-tx
%- ot %- ot
:~ [%txid (cu from-cord:hxb:bc so)] :~ [%txid (cu from-cord:hxb:bcu so)]
[%broadcast bo] [%broadcast bo]
[%included bo] [%included bo]
== ==
@ -470,8 +471,8 @@
%- ot %- ot
:~ [%block ni] :~ [%block ni]
[%fee (mu ni)] [%fee (mu ni)]
[%blockhash (cu from-cord:hxb:bc so)] [%blockhash (cu from-cord:hxb:bcu so)]
[%blockfilter (cu from-cord:hxb:bc so)] [%blockfilter (cu from-cord:hxb:bcu so)]
== ==
-- --
-- --
@ -488,17 +489,17 @@
%get-tx-vals %get-tx-vals
%- get-request %- get-request
%+ mk-url '/gettxvals/' %+ mk-url '/gettxvals/'
(to-cord:hxb:bc txid.ract) (to-cord:hxb:bcu txid.ract)
:: ::
%get-raw-tx %get-raw-tx
%- get-request %- get-request
%+ mk-url '/getrawtx/' %+ mk-url '/getrawtx/'
(to-cord:hxb:bc txid.ract) (to-cord:hxb:bcu txid.ract)
:: ::
%broadcast-tx %broadcast-tx
%- get-request %- get-request
%+ mk-url '/broadcasttx/' %+ mk-url '/broadcasttx/'
(to-cord:hxb:bc rawtx.ract) (to-cord:hxb:bcu rawtx.ract)
:: ::
%get-block-count %get-block-count
%- get-request %- get-request

View File

@ -2,8 +2,6 @@
|% |%
+$ cache-action +$ cache-action
$% [%graph-to-mark (pair resource:res (unit mark))] $% [%graph-to-mark (pair resource:res (unit mark))]
[%perm-marks (pair (pair mark @tas) tube:clay)]
[%transform-marks (pair mark tube:clay)]
== ==
-- --
:: ::

View File

@ -0,0 +1,12 @@
/- *post
|_ i=indexed-post
++ grad %noun
++ grow
|%
++ noun i
--
++ grab
|%
++ noun indexed-post
--
--

View File

@ -24,6 +24,7 @@
$% [%set-credentials api-url=@t =network] $% [%set-credentials api-url=@t =network]
[%add-whitelist wt=whitelist-target] [%add-whitelist wt=whitelist-target]
[%remove-whitelist wt=whitelist-target] [%remove-whitelist wt=whitelist-target]
[%set-interval inte=@dr]
== ==
+$ action +$ action
$% [%address-info =address] $% [%address-info =address]

View File

@ -162,6 +162,10 @@
[%new-address =address] [%new-address =address]
[%balance balance=(unit [confirmed=sats unconfirmed=sats])] [%balance balance=(unit [confirmed=sats unconfirmed=sats])]
[%error =error] [%error =error]
:: current index being scanned in each wallet part
:: ~ if scan of that part is done
::
[%scan-progress main=(unit idx) change=(unit idx)]
== ==
:: ::
-- --

View File

@ -10,5 +10,7 @@
:: ::
[%warm-cache-all ~] [%warm-cache-all ~]
[%cool-cache-all ~] [%cool-cache-all ~]
[%warm-static-conversion from=term to=term]
[%cool-static-conversion from=term to=term]
== ==
-- --

View File

@ -1,7 +1,7 @@
:: Note: these are for BTC testnet :: Note: these are for BTC testnet
:: ::
/- spider, rpc=json-rpc /- spider, rpc=json-rpc
/+ strandio, bc=bitcoin /+ strandio, bc=bitcoin, bcu=bitcoin-utils
=, strand=strand:spider =, strand=strand:spider
=> =>
|% |%
@ -47,8 +47,8 @@
++ electrs-script-hash ++ electrs-script-hash
|= a=address:bc |= a=address:bc
^- hexb:bc ^- hexb:bc
%- flip:byt:bc %- flip:byt:bcu
%- sha256:bc %- sha256:bcu
(to-script-pubkey:adr:bc a) (to-script-pubkey:adr:bc a)
:: ::
++ parse-json-rpc ++ parse-json-rpc

View File

@ -4,42 +4,34 @@
|% |%
++ strand strand:spider ++ strand strand:spider
++ poke poke:strandio ++ poke poke:strandio
++ poke-our poke-our:strandio ++ raw-poke-our raw-poke-our:strandio
:: ::
++ scry-metadata ++ scry-metadata
|= rid=resource |= rid=resource
=/ m (strand ,resource)
^- form:m
;< group=(unit resource) bind:m
%+ scry:strandio ,(unit resource) %+ scry:strandio ,(unit resource)
;: weld ;: weld
/gx/metadata-store/resource/graph /gx/metadata-store/resource/graph
(en-path:resource rid) (en-path:resource rid)
/noun /noun
== ==
(pure:m (need group))
:: ::
++ scry-group ++ scry-group
|= rid=resource |= rid=resource
=/ m (strand ,group)
^- form:m
;< ugroup=(unit group) bind:m
%+ scry:strandio ,(unit group) %+ scry:strandio ,(unit group)
;: weld ;: weld
/gx/group-store/groups /gx/group-store/groups
(en-path:resource rid) (en-path:resource rid)
/noun /noun
== ==
(pure:m (need ugroup))
:: ::
++ delete-graph ++ delete-graph
|= [now=time rid=resource] |= [now=time rid=resource]
=/ m (strand ,~) =/ m (strand ,~)
^- form:m ^- form:m
;< ~ bind:m ;< ~ bind:m
(poke-our %graph-pull-hook %pull-hook-action !>([%remove rid])) (raw-poke-our %graph-pull-hook %pull-hook-action !>([%remove rid]))
;< ~ bind:m ;< ~ bind:m
(poke-our %graph-store %graph-update-2 !>([now [%remove-graph rid]])) (raw-poke-our %graph-store %graph-update-2 !>([now [%remove-graph rid]]))
(pure:m ~) (pure:m ~)
-- --
:: ::
@ -52,10 +44,12 @@
;< =bowl:spider bind:m get-bowl:strandio ;< =bowl:spider bind:m get-bowl:strandio
?: =(our.bowl entity.rid.action) ?: =(our.bowl entity.rid.action)
(strand-fail:strandio %bad-request ~) (strand-fail:strandio %bad-request ~)
;< group-rid=resource bind:m (scry-metadata rid.action) ;< group-rid=(unit resource) bind:m (scry-metadata rid.action)
;< g=group bind:m (scry-group group-rid) ?~ group-rid (pure:m !>(~))
;< g=(unit group) bind:m (scry-group u.group-rid)
?~ g (pure:m !>(~))
;< ~ bind:m (delete-graph now.bowl rid.action) ;< ~ bind:m (delete-graph now.bowl rid.action)
?. hidden.g ?. hidden.u.g
(pure:m !>(~)) (pure:m !>(~))
;< =thread-result:strandio bind:m ;< =thread-result:strandio bind:m
(await-thread:strandio %group-leave !>([~ [%leave rid.action]])) (await-thread:strandio %group-leave !>([~ [%leave rid.action]]))

View File

@ -41,6 +41,7 @@
date-created now.bowl date-created now.bowl
creator our.bowl creator our.bowl
config [%group ~] config [%group ~]
hidden %.n
== ==
=/ met-action=action:metadata =/ met-action=action:metadata
[%add rid groups+rid metadatum] [%add rid groups+rid metadatum]

View File

@ -1,4 +1,4 @@
/+ *test, *bitcoin, bip32 /+ *test, *bitcoin, *bitcoin-utils, bip32
=, secp:crypto =, secp:crypto
=+ ecc=secp256k1 =+ ecc=secp256k1
|% |%

View File

@ -0,0 +1,27 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": ["react"],
"rules": {
"react/prop-types": 0
},
"settings": {
"react": {
"version": "detect"
}
}
}

1
pkg/btc-wallet/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.eslintcache

1
pkg/btc-wallet/.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/_/husky.sh"
cd pkg/interface && npx lint-staged cd pkg/btc-wallet && npx lint-staged src

View File

@ -0,0 +1,4 @@
{
"semi": true,
"singleQuote": true
}

View File

@ -5,4 +5,4 @@ dojo:
it should return with the following hash: it should return with the following hash:
`0v1.9p61c.bd4vn.deevh.0ldbq.fkqo3` `0v7.v4dng.o33qi.kc497.5jc02.ke5es`

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
{ {
"name": "urbit-bitcoin-wallet", "name": "btc-wallet",
"version": "0.1.0", "version": "0.1.0",
"main": "node install.js", "main": "node install.js",
"private": true,
"scripts": { "scripts": {
"start": "webpack-dev-server --config config/webpack.dev.js", "start": "webpack-dev-server --config config/webpack.dev.js",
"build:dev": "cross-env NODE_ENV=production webpack --config config/webpack.dev.js", "build:dev": "cross-env NODE_ENV=production webpack --config config/webpack.dev.js",
@ -24,8 +25,14 @@
"babel-plugin-root-import": "^6.5.0", "babel-plugin-root-import": "^6.5.0",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.24.0",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"html-webpack-plugin": "^4.2.0", "html-webpack-plugin": "^4.2.0",
"husky": "^6.0.0",
"lint-staged": "^11.0.0",
"prettier": "^2.3.1",
"react-hot-loader": "^4.12.21", "react-hot-loader": "^4.12.21",
"sass": "^1.26.5", "sass": "^1.26.5",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
@ -71,5 +78,9 @@
}, },
"resolutions": { "resolutions": {
"natives": "1.1.3" "natives": "1.1.3"
},
"lint-staged": {
"*.js": "eslint --cache --fix",
"*.{js,css,md}": "prettier --write"
} }
} }

View File

@ -10,8 +10,6 @@ import './css/custom.css';
// rebuild x3 // rebuild x3
window.NETWORK = 'testnet'; // 'bitcoin'
const channel = new window.channel(); const channel = new window.channel();
api.setChannel(window.ship, channel); api.setChannel(window.ship, channel);

View File

@ -7,10 +7,13 @@ class UrbitApi {
this.bindPaths = []; this.bindPaths = [];
} }
bind(path, method, ship = this.ship, appl = "btc-wallet", success, fail) { bind(path, method, ship = this.ship, appl = 'btc-wallet', success, fail) {
this.bindPaths = _.uniq([...this.bindPaths, path]); this.bindPaths = _.uniq([...this.bindPaths, path]);
window.subscriptionId = this.channel.subscribe(ship, appl, path, window.subscriptionId = this.channel.subscribe(
ship,
appl,
path,
(err) => { (err) => {
fail(err); fail(err);
}, },
@ -19,32 +22,38 @@ class UrbitApi {
data: event, data: event,
from: { from: {
ship, ship,
path path,
} },
}); });
}, },
(err) => { (err) => {
fail(err); fail(err);
}); }
);
} }
btcWalletCommand(data) { btcWalletCommand(data) {
return this.action("btc-wallet", "btc-wallet-command", data); return this.action('btc-wallet', 'btc-wallet-command', data);
} }
settingsEvent(data) { settingsEvent(data) {
return this.action("settings-store", "settings-event", data); return this.action('settings-store', 'settings-event', data);
} }
action(appl, mark, data) { action(appl, mark, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.channel.poke(ship, appl, mark, data, this.channel.poke(
this.ship,
appl,
mark,
data,
(json) => { (json) => {
resolve(json); resolve(json);
}, },
(err) => { (err) => {
reject(err); reject(err);
}); }
);
}); });
} }
} }

View File

@ -1,17 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { import { Row, Text, Button, Col } from '@tlon/indigo-react';
Box, import Send from './send.js';
Icon, import CurrencyPicker from './currencyPicker.js';
Row, import { satsToCurrency } from '../../lib/util.js';
Text, import { store } from '../../store.js';
Button,
Col,
} from '@tlon/indigo-react';
import Send from './send.js'
import CurrencyPicker from './currencyPicker.js'
import { currencyToSats, satsToCurrency } from '../../lib/util.js';
export default class Balance extends Component { export default class Balance extends Component {
constructor(props) { constructor(props) {
@ -21,7 +13,7 @@ export default class Balance extends Component {
sending: false, sending: false,
copiedButton: false, copiedButton: false,
copiedString: false, copiedString: false,
} };
this.copyAddress = this.copyAddress.bind(this); this.copyAddress = this.copyAddress.bind(this);
} }
@ -29,39 +21,49 @@ export default class Balance extends Component {
copyAddress(arg) { copyAddress(arg) {
let address = this.props.state.address; let address = this.props.state.address;
navigator.clipboard.writeText(address); navigator.clipboard.writeText(address);
this.props.api.btcWalletCommand({'gen-new-address': null}); this.props.api.btcWalletCommand({ 'gen-new-address': null });
if (arg === 'button'){ if (arg === 'button') {
this.setState({copiedButton: true}); this.setState({ copiedButton: true });
setTimeout(() => { this.setState({copiedButton: false}); }, 2000); setTimeout(() => {
this.setState({ copiedButton: false });
}, 2000);
} else if (arg === 'string') { } else if (arg === 'string') {
this.setState({copiedString: true}); this.setState({ copiedString: true });
setTimeout(() => { this.setState({copiedString: false}); }, 2000); setTimeout(() => {
this.setState({ copiedString: false });
}, 2000);
} }
} }
render() { render() {
const sats = (this.props.state.confirmedBalance || 0); const sats = this.props.state.confirmedBalance || 0;
const unconfirmedSats = this.props.state.unconfirmedBalance; const unconfirmedSats = this.props.state.unconfirmedBalance;
const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : ''; const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : '';
const denomination = this.props.state.denomination; const denomination = this.props.state.denomination;
const value = satsToCurrency(sats, denomination, this.props.state.currencyRates); const value = satsToCurrency(
const sendDisabled = (sats === 0); sats,
const addressText = (this.props.state.address === null) ? '' : denomination,
this.props.state.address.slice(0, 6) + '...' + this.props.state.currencyRates
);
const sendDisabled = sats === 0;
const addressText =
this.props.state.address === null
? ''
: this.props.state.address.slice(0, 6) +
'...' +
this.props.state.address.slice(-6); this.props.state.address.slice(-6);
const conversion = this.props.state.currencyRates[denomination].last; const conversion = this.props.state.currencyRates[denomination].last;
return ( return (
<> <>
{this.state.sending ? {this.state.sending ? (
<Send <Send
state={this.props.state} state={this.props.state}
api={api} api={this.props.api}
psbt={this.props.state.psbt} psbt={this.props.state.psbt}
fee={this.props.state.fee} fee={this.props.state.fee}
currencyRates={this.props.state.currencyRates} currencyRates={this.props.state.currencyRates}
@ -73,13 +75,16 @@ export default class Balance extends Component {
network={this.props.network} network={this.props.network}
error={this.props.state.error} error={this.props.state.error}
stopSending={() => { stopSending={() => {
this.setState({sending: false}); this.setState({ sending: false });
store.handleEvent({data: {psbt: '', fee: 0, error: '', "broadcast-fail": null}}); store.handleEvent({
data: { psbt: '', fee: 0, error: '', 'broadcast-fail': null },
});
}} }}
/> : />
) : (
<Col <Col
height="400px" height="400px"
width='100%' width="100%"
backgroundColor="white" backgroundColor="white"
borderRadius="48px" borderRadius="48px"
justifyContent="space-between" justifyContent="space-between"
@ -87,10 +92,19 @@ export default class Balance extends Component {
p={5} p={5}
> >
<Row justifyContent="space-between"> <Row justifyContent="space-between">
<Text color="orange" fontSize={1}>Balance</Text> <Text color="orange" fontSize={1}>
<Text color="lightGray" fontSize="14px" mono style={{cursor: "pointer"}} Balance
onClick={() => {this.copyAddress('string')}}> </Text>
{this.state.copiedString ? "copied" : addressText} <Text
color="lightGray"
fontSize="14px"
mono
style={{ cursor: 'pointer' }}
onClick={() => {
this.copyAddress('string');
}}
>
{this.state.copiedString ? 'copied' : addressText}
</Text> </Text>
<CurrencyPicker <CurrencyPicker
api={this.props.api} api={this.props.api}
@ -99,40 +113,57 @@ export default class Balance extends Component {
/> />
</Row> </Row>
<Col justifyContent="center" alignItems="center"> <Col justifyContent="center" alignItems="center">
<Text fontSize="40px" color="orange" style={{whiteSpace: "nowrap"}} > <Text
fontSize="40px"
color="orange"
style={{ whiteSpace: 'nowrap' }}
>
{value} {value}
</Text> </Text>
<Text fontSize={1} color="orange">{`${sats}${unconfirmedString} sats`}</Text> <Text
fontSize={1}
color="orange"
>{`${sats}${unconfirmedString} sats`}</Text>
</Col> </Col>
<Row flexDirection="row-reverse"> <Row flexDirection="row-reverse">
<Button children="Send" <Button
disabled={sendDisabled} disabled={sendDisabled}
fontSize={1} fontSize={1}
fontWeight="bold" fontWeight="bold"
color={sendDisabled ? "lighterGray" : "white"} color={sendDisabled ? 'lighterGray' : 'white'}
backgroundColor={sendDisabled ? "veryLightGray" : "orange"} backgroundColor={sendDisabled ? 'veryLightGray' : 'orange'}
style={{cursor: sendDisabled ? "default" : "pointer" }} style={{ cursor: sendDisabled ? 'default' : 'pointer' }}
borderColor="none" borderColor="none"
borderRadius="24px" borderRadius="24px"
height="48px" height="48px"
onClick={() => this.setState({sending: true})} onClick={() => this.setState({ sending: true })}
/> >
<Button children={(this.state.copiedButton) ? "Address Copied!" : "Copy Address"} Send
</Button>
<Button
mr={3} mr={3}
disabled={this.state.copiedButton} disabled={this.state.copiedButton}
fontSize={1} fontSize={1}
fontWeight="bold" fontWeight="bold"
color={(this.state.copiedButton) ? "green" : "orange"} color={this.state.copiedButton ? 'green' : 'orange'}
backgroundColor={(this.state.copiedButton) ? "veryLightGreen" : "midOrange" } backgroundColor={
style={{cursor: (this.state.copiedButton) ? "default" : "pointer"}} this.state.copiedButton ? 'veryLightGreen' : 'midOrange'
}
style={{
cursor: this.state.copiedButton ? 'default' : 'pointer',
}}
borderColor="none" borderColor="none"
borderRadius="24px" borderRadius="24px"
height="48px" height="48px"
onClick={() => {this.copyAddress('button')}} onClick={() => {
/> this.copyAddress('button');
}}
>
{this.state.copiedButton ? 'Address Copied!' : 'Copy Address'}
</Button>
</Row> </Row>
</Col> </Col>
} )}
</> </>
); );
} }

View File

@ -6,7 +6,6 @@ import {
StatelessTextInput, StatelessTextInput,
Icon, Icon,
Row, Row,
Input,
LoadingSpinner, LoadingSpinner,
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
@ -23,7 +22,7 @@ export default class ProviderModal extends Component {
ready: false, ready: false,
provider: null, provider: null,
connecting: false, connecting: false,
} };
this.checkProvider = this.checkProvider.bind(this); this.checkProvider = this.checkProvider.bind(this);
this.submitProvider = this.submitProvider.bind(this); this.submitProvider = this.submitProvider.bind(this);
@ -38,77 +37,88 @@ export default class ProviderModal extends Component {
if (isValidPatp(provider)) { if (isValidPatp(provider)) {
let command = { let command = {
"check-provider": provider 'check-provider': provider,
} };
potentialProvider = provider; potentialProvider = provider;
checkingProvider = true; checkingProvider = true;
this.props.api.btcWalletCommand(command); this.props.api.btcWalletCommand(command);
setTimeout(() => { setTimeout(() => {
this.setState({providerFailed: true, checkingProvider: false}); this.setState({ providerFailed: true, checkingProvider: false });
}, 5000); }, 5000);
} }
this.setState({provider, ready, checkingProvider, potentialProvider}); this.setState({ provider, ready, checkingProvider, potentialProvider });
} }
componentDidUpdate(prevProps, prevState){ componentDidUpdate() {
if (!this.state.ready){ if (!this.state.ready) {
if (this.props.providerPerms[this.state.provider]) { if (this.props.providerPerms[this.state.provider]) {
this.setState({ready: true, checkingProvider: false, providerFailed: false}); this.setState({
ready: true,
checkingProvider: false,
providerFailed: false,
});
} }
} }
} }
submitProvider(e){ submitProvider() {
if (this.state.ready){ if (this.state.ready) {
let command = { let command = {
"set-provider": this.state.provider 'set-provider': this.state.provider,
} };
this.props.api.btcWalletCommand(command); this.props.api.btcWalletCommand(command);
this.setState({connecting: true}); this.setState({ connecting: true });
} }
} }
render() { render() {
let workingNode = null; let workingNode = null;
let workingColor = null; let workingColor = null;
let workingBg = null; let workingBg = null;
if (this.state.ready) { if (this.state.ready) {
workingColor = "green"; workingColor = 'green';
workingBg = "veryLightGreen" workingBg = 'veryLightGreen';
workingNode = workingNode = (
<Box mt={3}> <Box mt={3}>
<Text fontSize="14px" color="green"> <Text fontSize="14px" color="green">
{this.state.provider} is a working provider node {this.state.provider} is a working provider node
</Text> </Text>
</Box> </Box>
);
} else if (this.state.providerFailed) { } else if (this.state.providerFailed) {
workingColor = "red"; workingColor = 'red';
workingBg = "veryLightRed" workingBg = 'veryLightRed';
workingNode = workingNode = (
<Box mt={3}> <Box mt={3}>
<Text fontSize="14px" color="red"> <Text fontSize="14px" color="red">
{this.state.potentialProvider} is not a working provider node {this.state.potentialProvider} is not a working provider node
</Text> </Text>
</Box> </Box>
);
} }
return ( return (
<Box <Box width="100%" height="100%" padding={3}>
width="100%"
height="100%"
padding={3}
>
<Row> <Row>
<Icon icon="Bitcoin" mr={2}/> <Icon icon="Bitcoin" mr={2} />
<Text fontSize="14px" fontWeight="bold"> <Text fontSize="14px" fontWeight="bold">
Step 1 of 2: Set up Bitcoin Provider Node Step 1 of 2: Set up Bitcoin Provider Node
</Text> </Text>
</Row> </Row>
<Box mt={3}> <Box mt={3}>
<Text fontSize="14px" fontWeight="regular" color="gray"> <Text fontSize="14px" fontWeight="regular" color="gray">
In order to perform Bitcoin transaction in Landscape, you'll need to set a provider node. A provider node is an urbit which maintains a synced Bitcoin ledger. In order to perform Bitcoin transaction in Landscape, you&apos;ll
<a fontSize="14px" target="_blank" href="https://urbit.org/bitcoin-wallet"> Learn More</a> need to set a provider node. A provider node is an urbit which
maintains a synced Bitcoin ledger.
<a
fontSize="14px"
target="_blank"
href="https://urbit.org/bitcoin-wallet"
rel="noreferrer"
>
{' '}
Learn More
</a>
</Text> </Text>
</Box> </Box>
<Box mt={3} mb={2}> <Box mt={3} mb={2}>
@ -132,7 +142,7 @@ export default class ProviderModal extends Component {
borderColor={workingColor} borderColor={workingColor}
onChange={this.checkProvider} onChange={this.checkProvider}
/> />
{(this.state.checkingProvider) ? <LoadingSpinner/> : null} {this.state.checkingProvider ? <LoadingSpinner /> : null}
</Row> </Row>
{workingNode} {workingNode}
<Row alignItems="center" mt={3}> <Row alignItems="center" mt={3}>
@ -140,12 +150,15 @@ export default class ProviderModal extends Component {
mr={2} mr={2}
primary primary
disabled={!this.state.ready} disabled={!this.state.ready}
children="Set Peer Node"
fontSize="14px" fontSize="14px"
style={{cursor: this.state.ready ? "pointer" : "default"}} style={{ cursor: this.state.ready ? 'pointer' : 'default' }}
onClick={() => {this.submitProvider(this.state.provider)}} onClick={() => {
/> this.submitProvider(this.state.provider);
{(this.state.connecting) ? <LoadingSpinner/> : null} }}
>
Set Peer Node
</Button>
{this.state.connecting ? <LoadingSpinner /> : null}
</Row> </Row>
</Box> </Box>
); );

View File

@ -1,57 +1,56 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react';
Box,
Icon,
Row,
Text,
Button,
Col,
LoadingSpinner,
} from '@tlon/indigo-react';
import { Sigil } from './sigil.js'
export default class TxAction extends Component { export default class TxAction extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
} }
render() { render() {
const leftIcon = const leftIcon =
(this.props.action === "sent") ? "ArrowSouth" : this.props.action === 'sent'
(this.props.action === "recv") ? "ArrowNorth" : ? 'ArrowSouth'
(this.props.action === "fail") ? "X" : : this.props.action === 'recv'
"NullIcon"; ? 'ArrowNorth'
: this.props.action === 'fail'
? 'X'
: 'NullIcon';
const actionColor = const actionColor =
(this.props.action === "sent") ? "sentBlue" : this.props.action === 'sent'
(this.props.action === "recv") ? "recvGreen" : ? 'sentBlue'
(this.props.action === "fail") ? "gray" : : this.props.action === 'recv'
"red"; ? 'recvGreen'
: this.props.action === 'fail'
? 'gray'
: 'red';
const actionText = const actionText =
(this.props.action === "sent" && !this.props.pending) ? "Sent BTC" : this.props.action === 'sent' && !this.props.pending
(this.props.action === "sent" && this.props.pending) ? "Sending BTC" : ? 'Sent BTC'
(this.props.action === "recv" && !this.props.pending) ? "Received BTC" : : this.props.action === 'sent' && this.props.pending
(this.props.action === "recv" && this.props.pending) ? "Receiving BTC" : ? 'Sending BTC'
(this.props.action === "fail") ? "Failed" : : this.props.action === 'recv' && !this.props.pending
"error"; ? 'Received BTC'
: this.props.action === 'recv' && this.props.pending
? 'Receiving BTC'
: this.props.action === 'fail'
? 'Failed'
: 'error';
const pending = (!this.props.pending) ? null : const pending = !this.props.pending ? null : (
<LoadingSpinner <LoadingSpinner background="midOrange" foreground="orange" />
background="midOrange" );
foreground="orange"
/>
const url = (this.props.network === 'testnet') const url =
this.props.network === 'testnet'
? `http://blockstream.info/testnet/tx/${this.props.txid}` ? `http://blockstream.info/testnet/tx/${this.props.txid}`
: `http://blockstream.info/tx/${this.props.txid}`; : `http://blockstream.info/tx/${this.props.txid}`;
return ( return (
<Row alignItems="center"> <Row alignItems="center">
<Box backgroundColor={actionColor} <Box
backgroundColor={actionColor}
width="24px" width="24px"
height="24px" height="24px"
textAlign="center" textAlign="center"
@ -60,11 +59,13 @@ export default class TxAction extends Component {
mr={2} mr={2}
p={1} p={1}
> >
<Icon icon={leftIcon} color="white"/> <Icon icon={leftIcon} color="white" />
</Box> </Box>
<Text color={actionColor} fontSize="14px">{actionText}</Text> <Text color={actionColor} fontSize="14px">
<a href={url} target="_blank"> {actionText}
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2}/> </Text>
<a href={url} target="_blank" rel="noreferrer">
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2} />
</a> </a>
{pending} {pending}
</Row> </Row>

View File

@ -1,28 +1,16 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { BrowserRouter, Route } from "react-router-dom"; import { BrowserRouter } from 'react-router-dom';
import _ from 'lodash';
import { api } from '../api.js'; import { api } from '../api.js';
import { store } from '../store.js'; import { store } from '../store.js';
import { ThemeProvider } from 'styled-components';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import light from './themes/light'; import light from './themes/light';
import dark from './themes/dark'; // import dark from './themes/dark';
import { import { Box, Reset } from '@tlon/indigo-react';
Text,
Box,
Reset,
Col,
LoadingSpinner,
} from '@tlon/indigo-react';
import StartupModal from './lib/startupModal.js'; import StartupModal from './lib/startupModal.js';
import Header from './lib/header.js' import Body from './lib/body.js';
import Balance from './lib/balance.js' import { subscription } from '../subscription.js';
import Transactions from './lib/transactions.js'
import Warning from './lib/warning.js'
import Body from './lib/body.js'
import { subscription } from '../subscription.js'
const network = "bitcoin"; const network = 'bitcoin';
export class Root extends Component { export class Root extends Component {
constructor(props) { constructor(props) {
@ -32,8 +20,8 @@ export class Root extends Component {
store.setStateHandler(this.setState.bind(this)); store.setStateHandler(this.setState.bind(this));
} }
componentDidMount(){ componentDidMount() {
this.props.channel.setOnChannelError((e) => { this.props.channel.setOnChannelError(() => {
subscription.start(); subscription.start();
}); });
subscription.start(); subscription.start();
@ -42,34 +30,38 @@ export class Root extends Component {
render() { render() {
const loaded = this.state.loaded; const loaded = this.state.loaded;
const warning = this.state.showWarning; const warning = this.state.showWarning;
const blur = (!loaded) ? false : const blur = !loaded ? false : !(this.state.wallet && this.state.provider);
!(this.state.wallet && this.state.provider);
return ( return (
<BrowserRouter> <BrowserRouter>
<ThemeProvider theme={light}> <ThemeProvider theme={light}>
<Reset/> <Reset />
{ (loaded) ? <StartupModal api={api} state={this.state} network={network}/> : null } {loaded ? (
<Box display="flex" <StartupModal api={api} state={this.state} network={network} />
flexDirection='column' ) : null}
position='absolute' <Box
alignItems='center' display="flex"
backgroundColor='lightOrange' flexDirection="column"
width='100%' position="absolute"
alignItems="center"
backgroundColor="lightOrange"
width="100%"
minHeight={loaded ? '100%' : 'none'} minHeight={loaded ? '100%' : 'none'}
height={loaded ? 'none' : '100%'} height={loaded ? 'none' : '100%'}
style={{filter: (blur ? 'blur(8px)' : 'none')}} style={{ filter: blur ? 'blur(8px)' : 'none' }}
px={[0,4]} px={[0, 4]}
pb={[0,4]} pb={[0, 4]}
> >
<Body loaded={loaded} <Body
loaded={loaded}
state={this.state} state={this.state}
api={api} network={network} api={api}
network={network}
warning={warning} warning={warning}
/> />
</Box> </Box>
</ThemeProvider> </ThemeProvider>
</BrowserRouter> </BrowserRouter>
) );
} }
} }

View File

@ -2,5 +2,11 @@ module.exports = {
extends: '@urbit', extends: '@urbit',
env: { env: {
'jest': true 'jest': true
},
rules: {
// Because we use styled system, and use
// the convention of each prop on a new line
// we probably shouldn't keep this on
'max-lines-per-function': ['off', {}]
} }
}; };

View File

@ -36,8 +36,34 @@ export const globalTypes = {
export const decorators = [ export const decorators = [
(Story, context) => { (Story, context) => {
window.ship = 'sampel-palnet';
const theme = context.globals.theme === 'light' ? light : dark; const theme = context.globals.theme === 'light' ? light : dark;
useContactState.setState({
contacts: {
'~ridlur-figbud': {
status: 'please like and subscribe',
'last-updated': 1616609090555,
avatar: null,
cover: null,
bio: '',
nickname: 'Gav',
color: '0x26.3e0f',
groups: [],
},
'~sampel-palnet': {
status: 'A test status',
'last-updated': 1616609090555,
avatar: null,
cover: null,
bio: '',
nickname: 'You',
color: '0x26.3e0f',
groups: [],
},
},
});
useMetadataState.setState({ useMetadataState.setState({
associations: { associations: {
groups: { groups: {
@ -66,6 +92,25 @@ export const decorators = [
}, },
}, },
graph: { graph: {
'/ship/~bitbet-bolbel/links': {
metadata: {
preview: false,
vip: '',
title: 'Link Collection',
description: '',
creator: '~darrux-landes',
picture: '',
hidden: false,
config: {
graph: 'link',
},
'date-created': '~2020.4.6..21.53.30..dc68',
color: '0x0',
},
'app-name': 'graph',
resource: '/ship/~bitbet-bolbel/links',
group: '/ship/~bitbet-bolbel/urbit-community',
},
'/ship/~darrux-landes/development': { '/ship/~darrux-landes/development': {
metadata: { metadata: {
preview: false, preview: false,
@ -88,6 +133,47 @@ export const decorators = [
}, },
}, },
}, },
previews: {
'/ship/~bollug-worlus/urbit-index': {
group: '/ship/~bollug-worlus/urbit-index',
channels: {
'/ship/~darrux-landes/index-weekly': {
metadata: {
preview: false,
vip: '',
title: 'Index Weekly',
description: '',
creator: '~bollug-worlus',
picture: '',
hidden: false,
config: {
graph: 'publish',
},
'date-created': '~2020.4.6..21.53.30..dc68',
color: '0x0',
},
'app-name': 'graph',
resource: '/ship/~bollug-worlus/index-weekly',
group: '/ship/~bollug-worlus/urbit-index',
},
},
members: 1237,
metadata: {
preview: false,
vip: '',
title: 'Urbit Index',
description: '',
creator: '~bollug-worlus',
picture: '',
hidden: false,
config: {
group: null,
},
'date-created': '~2020.4.6..21.53.30..dc68',
color: '0x0',
},
},
},
}); });
useContactState.setState({ useContactState.setState({

View File

@ -28,12 +28,13 @@ module.exports = {
``` ```
Change the URL to your livenet ship (if making front-end changes) or keep it the Change the URL to your livenet ship (if making front-end changes) or keep it the
same (if [developing on a local development ship][local]). Then, from same (if [developing on a local development ship][local]). Then, from the root
'pkg/interface': of the repository
``` ```bash
npm ci npm i
npm run start npm run bootstrap
cd pkg/interface && npm run start
``` ```
The dev server will start at `http://localhost:9000`. Sign in as you would The dev server will start at `http://localhost:9000`. Sign in as you would

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"private": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@reach/disclosure": "^0.10.5", "@reach/disclosure": "^0.10.5",
@ -13,7 +14,8 @@
"@tlon/indigo-light": "^1.0.7", "@tlon/indigo-light": "^1.0.7",
"@tlon/indigo-react": "^1.2.23", "@tlon/indigo-react": "^1.2.23",
"@tlon/sigil-js": "^1.4.3", "@tlon/sigil-js": "^1.4.3",
"@urbit/api": "file:../npm/api", "@urbit/api": "^1.1.1",
"@urbit/http-api": "^1.2.1",
"any-ascii": "^0.1.7", "any-ascii": "^0.1.7",
"aws-sdk": "^2.830.0", "aws-sdk": "^2.830.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
@ -80,7 +82,7 @@
"@types/yup": "^0.29.11", "@types/yup": "^0.29.11",
"@typescript-eslint/eslint-plugin": "^4.15.0", "@typescript-eslint/eslint-plugin": "^4.15.0",
"@typescript-eslint/parser": "^4.24.0", "@typescript-eslint/parser": "^4.24.0",
"@urbit/eslint-config": "file:../npm/eslint-config", "@urbit/eslint-config": "^1.0.0",
"@welldone-software/why-did-you-render": "^6.1.0", "@welldone-software/why-did-you-render": "^6.1.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3", "babel-jest": "^26.6.3",
@ -102,6 +104,7 @@
"react-hot-loader": "^4.13.0", "react-hot-loader": "^4.13.0",
"sass": "^1.32.5", "sass": "^1.32.5",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"storybook-addon-designs": "^6.0.0",
"ts-mdast": "^1.0.0", "ts-mdast": "^1.0.0",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"webpack": "^4.46.0", "webpack": "^4.46.0",
@ -113,15 +116,15 @@
"lint-file": "eslint", "lint-file": "eslint",
"tsc": "tsc", "tsc": "tsc",
"tsc:watch": "tsc --watch", "tsc:watch": "tsc --watch",
"preinstall": "./preinstall.sh",
"build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js", "build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
"build:prod": "cd ../npm/api && npm ci && cd ../../interface && cross-env NODE_ENV=production webpack --config config/webpack.prod.js", "build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
"start": "webpack-dev-server --config config/webpack.dev.js", "start": "webpack-dev-server --config config/webpack.dev.js",
"test": "jest", "test": "tsc && jest",
"prepare": "cd ../.. && husky install pkg/interface/.husky", "jest": "jest",
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook", "build-storybook": "build-storybook",
"chromatic": "chromatic --exit-zero-on-changes" "chromatic": "chromatic --exit-zero-on-changes",
"hook-lint": "eslint --cache --fix"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",

View File

@ -10,3 +10,5 @@ for i in $(find . -type d -maxdepth 1) ; do
cd .. cd ..
fi fi
done done
cd http-api
npm run build

View File

@ -1,74 +0,0 @@
import { Path, Patp } from '@urbit/api';
import _ from 'lodash';
import BaseStore from '../store/base';
export default class BaseApi<S extends object = {}> {
bindPaths: Path[] = [];
constructor(public ship: Patp, public channel: any, public store: BaseStore<S>) {}
unsubscribe(id: number) {
this.channel.unsubscribe(id);
}
subscribe(path: Path, method, ship = this.ship, app: string, success, fail, quit, queue = false) {
this.bindPaths = _.uniq([...this.bindPaths, path]);
return this.channel.subscribe(
this.ship,
app,
path,
(err) => {
fail(err);
},
(event) => {
success({
data: event,
from: {
ship,
path
}
});
},
(qui) => {
quit(qui);
},
() => {},
queue
);
}
action(
appl: string,
mark: string,
data: any,
ship = (window as any).ship
): Promise<any> {
return new Promise((resolve, reject) => {
this.channel.poke(
ship,
appl,
mark,
data,
(json) => {
resolve(json);
},
(err) => {
reject(err);
}
);
});
}
scry<T>(app: string, path: Path): Promise<T> {
return fetch(`/~/scry/${app}${path}.json`).then(r => r.json() as Promise<T>);
}
async spider<T>(inputMark: string, outputMark: string, threadName: string, body: any): Promise<T> {
const res = await fetch(`/spider/${inputMark}/${threadName}/${outputMark}.json`, {
method: 'POST',
body: JSON.stringify(body)
});
return res.json();
}
}

View File

@ -0,0 +1,47 @@
import airlock from '~/logic/api';
import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';
import useContactState from '../state/contact';
import useGraphState from '../state/graph';
import useGroupState from '../state/group';
import useInviteState from '../state/invite';
import useLaunchState from '../state/launch';
import useSettingsState from '../state/settings';
import useLocalState from '../state/local';
import useStorageState from '../state/storage';
export async function bootstrapApi() {
airlock.onError = (e) => {
(async () => {
const { reconnect } = useLocalState.getState();
try {
await reconnect();
} catch (e) {
console.log(e);
console.log('onError');
}
})();
};
airlock.onRetry = () => {
useLocalState.setState({ subscription: 'reconnecting' });
};
airlock.onOpen = () => {
useLocalState.setState({ subscription: 'connected' });
};
const promises = [
useHarkState,
useMetadataState,
useGroupState,
useContactState,
useSettingsState,
useLaunchState,
useInviteState,
useGraphState,
useStorageState
].map(state => state.getState().initialize(airlock));
await Promise.all(promises);
}

View File

@ -1,124 +0,0 @@
import { Patp } from '@urbit/api';
import { ContactEditField } from '@urbit/api/contacts';
import _ from 'lodash';
import {edit} from '../reducers/contact-update';
import {doOptimistically} from '../state/base';
import useContactState from '../state/contact';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class ContactsApi extends BaseApi<StoreState> {
add(ship: Patp, contact: any) {
contact['last-updated'] = Date.now();
return this.storeAction({ add: { ship, contact } });
}
remove(ship: Patp) {
return this.storeAction({ remove: { ship } });
}
edit(ship: Patp, editField: ContactEditField) {
/* editField can be...
{nickname: ''}
{email: ''}
{phone: ''}
{website: ''}
{color: 'fff'} // with no 0x prefix
{avatar: null}
{avatar: ''}
{add-group: {ship, name}}
{remove-group: {ship, name}}
*/
const action = {
edit: {
ship,
'edit-field': editField,
timestamp: Date.now()
}
}
doOptimistically(useContactState, action, this.storeAction.bind(this), [edit])
}
allowShips(ships: Patp[]) {
return this.storeAction({
allow: {
ships
}
});
}
allowGroup(ship: string, name: string) {
const group = { ship, name };
return this.storeAction({
allow: {
group
}
});
}
setPublic(setPublic: any) {
return this.storeAction({
'set-public': setPublic
});
}
share(recipient: Patp) {
return this.action(
'contact-push-hook',
'contact-share',
{ share: recipient }
);
}
fetchIsAllowed(entity, name, ship, personal) {
const isPersonal = personal ? 'true' : 'false';
return this.scry<any>(
'contact-store',
`/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
);
}
async disallowedShipsForOurContact(ships: string[]): Promise<string[]> {
return _.compact(
await Promise.all(
ships.map(
async (s) => {
const ship = `~${s}`;
if(s === window.ship) {
return null;
}
const allowed = await this.fetchIsAllowed(
`~${window.ship}`,
'personal',
ship,
true
);
return allowed ? null : ship;
}
)
)
);
}
retrieve(ship: string) {
const resource = { ship, name: '' };
return this.action('contact-pull-hook', 'pull-hook-action', {
add: {
resource,
ship
}
});
}
private storeAction(action: any): Promise<any> {
return this.action('contact-store', 'contact-update-0', action);
}
private viewAction(threadName: string, action: any) {
return this.spider('contact-view-action', 'json', threadName, action);
}
private hookAction(ship: Patp, action: any): Promise<any> {
return this.action('contact-push-hook', 'contact-update-0', action);
}
}

View File

@ -1,19 +0,0 @@
import type { StoreState } from '../store/type';
import BaseApi from './base';
export default class GcpApi extends BaseApi<StoreState> {
// Does not touch the store; use the value manually.
async isConfigured(): Promise<boolean> {
return this.spider('noun', 'json', 'gcp-is-configured', {});
}
// Does not return the token; read it out of the store.
async getToken(): Promise<void> {
return this.spider('noun', 'gcp-token', 'gcp-get-token', {})
.then((token) => {
this.store.handleEvent({
data: token
});
});
}
}

View File

@ -1,38 +0,0 @@
import { Patp } from '@urbit/api';
import GlobalStore from '../store/store';
import { StoreState } from '../store/type';
import BaseApi from './base';
import ContactsApi from './contacts';
import GcpApi from './gcp';
import GraphApi from './graph';
import GroupsApi from './groups';
import { HarkApi } from './hark';
import InviteApi from './invite';
import LaunchApi from './launch';
import LocalApi from './local';
import MetadataApi from './metadata';
import S3Api from './s3';
import SettingsApi from './settings';
export default class GlobalApi extends BaseApi<StoreState> {
local = new LocalApi(this.ship, this.channel, this.store);
invite = new InviteApi(this.ship, this.channel, this.store);
metadata = new MetadataApi(this.ship, this.channel, this.store);
contacts = new ContactsApi(this.ship, this.channel, this.store);
groups = new GroupsApi(this.ship, this.channel, this.store);
launch = new LaunchApi(this.ship, this.channel, this.store);
gcp = new GcpApi(this.ship, this.channel, this.store);
s3 = new S3Api(this.ship, this.channel, this.store);
graph = new GraphApi(this.ship, this.channel, this.store);
hark = new HarkApi(this.ship, this.channel, this.store);
settings = new SettingsApi(this.ship, this.channel, this.store);
constructor(
public ship: Patp,
public channel: any,
public store: GlobalStore
) {
super(ship, channel, store);
}
}

View File

@ -1,456 +0,0 @@
import { patp2dec } from 'urbit-ob';
import { Content, Enc, GraphNode, GroupPolicy, Path, Patp, Post, Resource } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import _ from 'lodash';
import { decToUd, deSig, resourceAsPath, unixToDa } from '~/logic/lib/util';
import { makeResource, resourceFromPath } from '../lib/group';
import { StoreState } from '../store/type';
import BaseApi from './base';
export const createBlankNodeWithChildPost = (
parentIndex = '',
childIndex = '',
contents: Content[]
): GraphNode => {
const date = unixToDa(Date.now()).toString();
const nodeIndex = parentIndex + '/' + date;
const childGraph = {};
childGraph[childIndex] = {
post: {
author: `~${window.ship}`,
index: nodeIndex + '/' + childIndex,
'time-sent': Date.now(),
contents,
hash: null,
signatures: []
},
children: null
};
return {
post: {
author: `~${window.ship}`,
index: nodeIndex,
'time-sent': Date.now(),
contents: [],
hash: null,
signatures: []
},
children: childGraph as BigIntOrderedMap<GraphNode>
};
};
function markPending(nodes: any) {
_.forEach(nodes, (node) => {
node.post.author = deSig(node.post.author);
node.post.pending = true;
markPending(node.children || {});
});
}
export const createPost = (
contents: Content[],
parentIndex = '',
childIndex = 'DATE_PLACEHOLDER'
) => {
if (childIndex === 'DATE_PLACEHOLDER') {
childIndex = unixToDa(Date.now()).toString();
}
return {
author: `~${window.ship}`,
index: parentIndex + '/' + childIndex,
'time-sent': Date.now(),
contents,
hash: null,
signatures: []
};
};
function moduleToMark(mod: string): string | undefined {
if(mod === 'link') {
return 'graph-validator-link';
}
if(mod === 'publish') {
return 'graph-validator-publish';
}
if(mod === 'chat') {
return 'graph-validator-chat';
}
return undefined;
}
export default class GraphApi extends BaseApi<StoreState> {
joiningGraphs = new Set<string>();
private storeAction(action: any): Promise<any> {
return this.action('graph-store', 'graph-update-2', action);
}
private viewAction(threadName: string, action: any) {
return this.spider('graph-view-action', 'json', threadName, action);
}
private hookAction(ship: Patp, action: any): Promise<any> {
return this.action('graph-push-hook', 'graph-update-2', action);
}
createManagedGraph(
name: string,
title: string,
description: string,
group: Path,
mod: string
) {
const associated = { group: resourceFromPath(group) };
const resource = makeResource(`~${window.ship}`, name);
return this.viewAction('graph-create', {
'create': {
resource,
title,
description,
associated,
'module': mod,
mark: moduleToMark(mod)
}
});
}
createUnmanagedGraph(
name: string,
title: string,
description: string,
policy: Enc<GroupPolicy>,
mod: string
) {
const resource = makeResource(`~${window.ship}`, name);
return this.viewAction('graph-create', {
'create': {
resource,
title,
description,
associated: { policy },
'module': mod,
mark: moduleToMark(mod)
}
});
}
joinGraph(ship: Patp, name: string) {
const resource = makeResource(ship, name);
const rid = resourceAsPath(resource);
if(this.joiningGraphs.has(rid)) {
return Promise.resolve();
}
this.joiningGraphs.add(rid);
return this.viewAction('graph-join', {
join: {
resource,
ship
}
}).then((res) => {
this.joiningGraphs.delete(rid);
return res;
});
}
deleteGraph(name: string) {
const resource = makeResource(`~${window.ship}`, name);
return this.viewAction('graph-delete', {
'delete': {
resource
}
});
}
leaveGraph(ship: Patp, name: string) {
const resource = makeResource(ship, name);
return this.viewAction('graph-leave', {
'leave': {
resource
}
});
}
groupifyGraph(ship: Patp, name: string, toPath?: string) {
const resource = makeResource(ship, name);
const to = toPath && resourceFromPath(toPath);
return this.viewAction('graph-groupify', {
groupify: {
resource,
to
}
});
}
eval(cord: string): Promise<string[] | undefined> {
return this.spider('graph-view-action', 'tang', 'graph-eval', {
eval: cord
});
}
addGraph(ship: Patp, name: string, graph: any, mark: any) {
return this.storeAction({
'add-graph': {
resource: { ship, name },
graph,
mark
}
});
}
addDmMessage(ship: Patp, contents: Content[]) {
const post = createPost(contents, `/${patp2dec(ship)}`);
const action = {
'add-nodes': {
resource: { ship: `~${window.ship}`, name: 'dm-inbox' },
nodes: {
[post.index]: {
post,
children: null
}
}
}
};
this.action('dm-hook', 'graph-update-2', action);
markPending(action['add-nodes'].nodes);
action['add-nodes'].resource.ship =
action['add-nodes'].resource.ship.slice(1);
this.store.handleEvent({ data: {
'graph-update': action
} });
}
acceptDm(ship: Patp) {
return this.action('dm-hook', 'dm-hook-action', { 'accept' : ship });
}
declineDm(ship: Patp) {
return this.action('dm-hook', 'dm-hook-action', { 'decline' : ship });
}
setScreen(screen: boolean) {
return this.action('dm-hook', 'dm-hook-action', { screen });
}
addPost(ship: Patp, name: string, post: Post) {
const nodes = {};
nodes[post.index] = {
post,
children: null
};
return this.addNodes(ship, name, nodes);
}
addNode(ship: Patp, name: string, node: GraphNode) {
const nodes = {};
nodes[node.post.index] = node;
return this.addNodes(ship, name, nodes);
}
addNodes(ship: Patp, name: string, nodes: Object) {
const action = {
'add-nodes': {
resource: { ship, name },
nodes
}
};
const pendingPromise = this.spider(
'graph-update-2',
'graph-view-action',
'graph-add-nodes',
action
);
markPending(action['add-nodes'].nodes);
action['add-nodes'].resource.ship =
action['add-nodes'].resource.ship.slice(1);
this.store.handleEvent({ data: {
'graph-update': action
} });
return pendingPromise;
/* TODO: stop lying to our users about pending states
return pendingPromise.then((pendingHashes) => {
for (let index in action['add-nodes'].nodes) {
action['add-nodes'].nodes[index].post.hash =
pendingHashes['pending-indices'][index] || null;
}
this.store.handleEvent({ data: {
'graph-update': {
'pending-indices': pendingHashes['pending-indices'],
...action
}
} });
});
*/
}
async enableGroupFeed(group: Resource, vip: any = ''): Promise<Resource> {
const { resource } = await this.spider(
'graph-view-action',
'resource',
'graph-create-group-feed',
{
'create-group-feed': { resource: group, vip }
}
);
return resource;
}
async disableGroupFeed(group: Resource): Promise<void> {
await this.spider(
'graph-view-action',
'json',
'graph-disable-group-feed',
{
'disable-group-feed': { resource: group }
}
);
}
removePosts(ship: Patp, name: string, indices: string[]) {
return this.hookAction(ship, {
'remove-posts': {
resource: { ship, name },
indices
}
});
}
getKeys() {
return this.scry<any>('graph-store', '/keys')
.then((keys) => {
this.store.handleEvent({
data: keys
});
});
}
getTags() {
return this.scry<any>('graph-store', '/tags')
.then((tags) => {
this.store.handleEvent({
data: tags
});
});
}
getTagQueries() {
return this.scry<any>('graph-store', '/tag-queries')
.then((tagQueries) => {
this.store.handleEvent({
data: tagQueries
});
});
}
getGraph(ship: string, resource: string) {
return this.scry<any>('graph-store', `/graph/${ship}/${resource}`)
.then((graph) => {
this.store.handleEvent({
data: graph
});
});
}
async getNewest(ship: string, resource: string, count: number, index = '') {
const data = await this.scry<any>('graph-store', `/newest/${ship}/${resource}/${count}${index}`);
data['graph-update'].fetch = true;
this.store.handleEvent({ data });
}
async getOlderSiblings(ship: string, resource: string, count: number, index = '') {
const idx = index.split('/').map(decToUd).join('/');
const data = await this.scry<any>('graph-store',
`/node-siblings/older/${ship}/${resource}/${count}${idx}`
);
data['graph-update'].fetch = true;
this.store.handleEvent({ data });
}
async getYoungerSiblings(ship: string, resource: string, count: number, index = '') {
const idx = index.split('/').map(decToUd).join('/');
const data = await this.scry<any>('graph-store',
`/node-siblings/younger/${ship}/${resource}/${count}${idx}`
);
data['graph-update'].fetch = true;
this.store.handleEvent({ data });
}
async getShallowChildren(ship: string, name: string, index = '') {
const idx = index.split('/').map(decToUd).join('/');
const data = await this.scry<any>('graph-store',
`/shallow-children/${ship}/${name}${idx}`
);
data['graph-update'].fetch = true;
this.store.handleEvent({ data });
}
async getDeepOlderThan(ship: string, resource: string, startTime = null, count: number) {
const start = startTime ? decToUd(startTime) : 'null';
const data = await this.scry<any>('graph-store',
`/deep-nodes-older-than/${ship}/${resource}/${count}/${start}`
);
data['graph-update'].fetch = true;
const node = data['graph-update'];
this.store.handleEvent({
data: {
'graph-update-flat': node,
'graph-update': node
}
});
}
async getFirstborn(ship: string, resource: string, index = '') {
const idx = index.split('/').map(decToUd).join('/');
const data = await this.scry<any>('graph-store',
`/firstborn/${ship}/${resource}${idx}`
);
data['graph-update'].fetch = true;
const node = data['graph-update'];
this.store.handleEvent({
data: {
'graph-update-thread': {
index,
...node
},
'graph-update': node
}
});
}
getGraphSubset(ship: string, resource: string, start: string, end: string) {
return this.scry<any>(
'graph-store',
`/graph-subset/${ship}/${resource}/${end}/${start}`
).then((subset) => {
this.store.handleEvent({
data: subset
});
});
}
async getNode(ship: string, resource: string, index: string) {
const idx = index.split('/').map(decToUd).join('/');
const data = await this.scry<any>(
'graph-store',
`/node/${ship}/${resource}${idx}`
);
data['graph-update'].fetch = true;
const node = data['graph-update'];
this.store.handleEvent({
data: {
'graph-update-loose': node
}
});
}
}

View File

@ -1,100 +0,0 @@
import { Enc, Patp } from '@urbit/api';
import {
GroupAction,
GroupPolicy,
GroupPolicyDiff, Resource,
Tag
} from '@urbit/api/groups';
import { makeResource } from '../lib/group';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class GroupsApi extends BaseApi<StoreState> {
remove(resource: Resource, ships: Patp[]) {
return this.proxyAction({ removeMembers: { resource, ships } });
}
addTag(resource: Resource, tag: Tag, ships: Patp[]) {
return this.proxyAction({ addTag: { resource, tag, ships } });
}
removeTag(resource: Resource, tag: Tag, ships: Patp[]) {
return this.proxyAction({ removeTag: { resource, tag, ships } });
}
add(resource: Resource, ships: Patp[]) {
return this.proxyAction({ addMembers: { resource, ships } });
}
removeGroup(resource: Resource) {
return this.storeAction({ removeGroup: { resource } });
}
changePolicy(resource: Resource, diff: Enc<GroupPolicyDiff>) {
return this.proxyAction({ changePolicy: { resource, diff } });
}
join(ship: string, name: string) {
const resource = makeResource(ship, name);
return this.viewAction({ join: { resource, ship } });
}
create(name: string, policy: Enc<GroupPolicy>, title: string, description: string) {
return this.viewThread('group-create', {
create: {
name,
policy,
title,
description
}
});
}
deleteGroup(ship: string, name: string) {
const resource = makeResource(ship, name);
return this.viewThread('group-delete', {
remove: resource
});
}
leaveGroup(ship: string, name: string) {
const resource = makeResource(ship, name);
return this.viewThread('group-leave', {
leave: resource
});
}
invite(ship: string, name: string, ships: Patp[], description: string) {
const resource = makeResource(ship, name);
return this.viewThread('group-invite', {
invite: {
resource,
ships,
description
}
});
}
hide(resource: string) {
return this.viewAction({ hide: resource });
}
private proxyAction(action: GroupAction) {
return this.action('group-push-hook', 'group-update-0', action);
}
private storeAction(action: GroupAction) {
return this.action('group-store', 'group-update-0', action);
}
private viewThread(thread: string, action: any) {
return this.spider('group-view-action', 'json', thread, action);
}
private viewAction(action: any) {
return this.action('group-view', 'group-view-action', action);
}
}

View File

@ -1,233 +0,0 @@
import { Association, GraphNotifDescription, IndexedNotification, NotifIndex } from '@urbit/api';
import { BigInteger } from 'big-integer';
import { getParentIndex } from '../lib/notification';
import { dateToDa, decToUd } from '../lib/util';
import { reduce } from '../reducers/hark-update';
import { doOptimistically } from '../state/base';
import useHarkState from '../state/hark';
import { StoreState } from '../store/type';
import BaseApi from './base';
function getHarkSize() {
return useHarkState.getState().notifications.size ?? 0;
}
export class HarkApi extends BaseApi<StoreState> {
private harkAction(action: any): Promise<any> {
return this.action('hark-store', 'hark-action', action);
}
private graphHookAction(action: any) {
return this.action('hark-graph-hook', 'hark-graph-hook-action', action);
}
private groupHookAction(action: any) {
return this.action('hark-group-hook', 'hark-group-hook-action', action);
}
private actOnNotification(frond: string, intTime: BigInteger | undefined, index: NotifIndex) {
const time = intTime ? decToUd(intTime.toString()) : null;
return this.harkAction({
[frond]: {
time,
index
}
});
}
async setMentions(mentions: boolean) {
await this.graphHookAction({
'set-mentions': mentions
});
}
setWatchOnSelf(watchSelf: boolean) {
return this.graphHookAction({
'set-watch-on-self': watchSelf
});
}
setDoNotDisturb(dnd: boolean) {
return this.harkAction({
'set-dnd': dnd
});
}
async archive(intTime: BigInteger, index: NotifIndex) {
const time = intTime ? decToUd(intTime.toString()) : null;
const action = {
archive: {
time,
index
}
};
await doOptimistically(useHarkState, action, this.harkAction.bind(this), [reduce]);
}
read(time: BigInteger, index: NotifIndex) {
return this.harkAction({
'read-note': index
});
}
readIndex(index: NotifIndex) {
return this.harkAction({
'read-index': index
});
}
unread(time: BigInteger, index: NotifIndex) {
return this.actOnNotification('unread-note', time, index);
}
readGroup(group: string) {
return this.harkAction({
'read-group': group
});
}
readGraph(graph: string) {
return this.harkAction({
'read-graph': graph
});
}
dismissReadCount(graph: string, index: string) {
return this.harkAction({
'read-count': {
graph: {
graph,
index
}
}
});
}
markCountAsRead(association: Association, parent: string, description: GraphNotifDescription) {
const action = { 'read-count': {
graph: {
graph: association.resource,
group: association.group,
description,
index: parent
} }
};
doOptimistically(useHarkState, action, this.harkAction.bind(this), [reduce]);
}
markEachAsRead(association: Association, parent: string, child: string, description: GraphNotifDescription, mod: string) {
return this.harkAction({
'read-each': {
index:
{ graph:
{ graph: association.resource,
group: association.group,
description,
module: mod,
index: parent
}
},
target: child
}
});
}
dec(index: NotifIndex, ref: string) {
return this.harkAction({
dec: {
index,
ref
}
});
}
seen() {
return this.harkAction({ seen: null });
}
readAll() {
return this.harkAction({ 'read-all': null });
}
mute(notif: IndexedNotification) {
if('graph' in notif.index && 'graph' in notif.notification.contents) {
const { index } = notif;
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph);
if(!parentIndex) {
return Promise.resolve();
}
return this.ignoreGraph(index.graph.graph, parentIndex);
}
if('group' in notif.index) {
const { group } = notif.index.group;
return this.ignoreGroup(group);
}
return Promise.resolve();
}
unmute(notif: IndexedNotification) {
if('graph' in notif.index && 'graph' in notif.notification.contents) {
const { index } = notif;
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph);
if(!parentIndex) {
return Promise.resolve();
}
return this.listenGraph(index.graph.graph, parentIndex);
}
if('group' in notif.index) {
return this.listenGroup(notif.index.group.group);
}
return Promise.resolve();
}
ignoreGroup(group: string) {
return this.groupHookAction({
ignore: group
});
}
ignoreGraph(graph: string, index: string) {
return this.graphHookAction({
ignore: {
graph,
index
}
});
}
listenGroup(group: string) {
return this.groupHookAction({
listen: group
});
}
listenGraph(graph: string, index: string) {
return this.graphHookAction({
listen: {
graph,
index
}
});
}
async getMore(): Promise<boolean> {
const offset = getHarkSize();
const count = 3;
await this.getSubset(offset, count, false);
return offset === getHarkSize();
}
async getSubset(offset:number, count:number, isArchive: boolean) {
const where = isArchive ? 'archive' : 'inbox';
const data = await this.scry('hark-store', `/recent/${where}/${offset}/${count}`);
this.store.handleEvent({ data });
}
async getTimeSubset(start?: Date, end?: Date) {
const s = start ? dateToDa(start) : '-';
const e = end ? dateToDa(end) : '-';
const result = await this.scry('hark-hook', `/recent/${s}/${e}`);
this.store.handleEvent({
data: result
});
}
}

View File

@ -0,0 +1,8 @@
import Urbit from '@urbit/http-api';
const api = new Urbit('', '');
api.ship = window.ship;
// api.verbose = true;
// @ts-ignore TODO window typings
window.api = api;
export default api;

View File

@ -1,27 +0,0 @@
import { Serial } from '@urbit/api';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class InviteApi extends BaseApi<StoreState> {
accept(app: string, uid: Serial) {
return this.inviteAction({
accept: {
term: app,
uid
}
});
}
decline(app: string, uid: Serial) {
return this.inviteAction({
decline: {
term: app,
uid
}
});
}
private inviteAction(action) {
return this.action('invite-store', 'invite-action', action);
}
}

View File

@ -1,29 +0,0 @@
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class LaunchApi extends BaseApi<StoreState> {
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) {
return this.launchAction({ add: { name, tile } });
}
remove(name: string) {
return this.launchAction({ remove: name });
}
changeFirstTime(firstTime = true) {
return this.launchAction({ 'change-first-time': firstTime });
}
changeIsShown(name: string, isShown = true) {
return this.launchAction({ 'change-is-shown': { name, isShown } });
}
weather(location: string) {
return this.action('weather', 'json', location);
}
private launchAction(data) {
return this.action('launch', 'launch-action', data);
}
}

View File

@ -1,16 +0,0 @@
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class LocalApi extends BaseApi<StoreState> {
getBaseHash() {
this.scry<string>('file-server', '/clay/base/hash').then((baseHash) => {
this.store.handleEvent({ data: { baseHash } });
});
}
getRuntimeLag() {
return this.scry<boolean>('launch', '/runtime-lag').then((runtimeLag) => {
this.store.handleEvent({ data: { runtimeLag } });
});
}
}

View File

@ -1,108 +0,0 @@
import { Association, Metadata, MetadataUpdatePreview, Path } from '@urbit/api';
import { uxToHex } from '../lib/util';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class MetadataApi extends BaseApi<StoreState> {
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
const creator = `~${this.ship}`;
return this.metadataAction({
add: {
group,
resource: {
resource,
'app-name': appName
},
metadata: {
title,
description,
color,
'date-created': dateCreated,
creator,
config: { graph: moduleName },
picture: '',
hidden: false,
preview: false,
vip: ''
}
}
});
}
remove(appName: string, resource: string, group: string) {
return this.metadataAction({
remove: {
group,
resource: {
resource,
'app-name': appName
}
}
});
}
update(association: Association, newMetadata: Partial<Metadata>) {
const metadata = { ...association.metadata, ...newMetadata };
metadata.color = uxToHex(metadata.color);
return this.metadataAction({
add: {
group: association.group,
resource: {
resource: association.resource,
'app-name': association['app-name']
},
metadata
}
});
}
preview(group: string) {
return new Promise<MetadataUpdatePreview>((resolve, reject) => {
const tempChannel: any = new (window as any).channel();
let done = false;
setTimeout(() => {
if(done) {
return;
}
done = true;
tempChannel.delete();
reject(new Error('offline'));
}, 15000);
tempChannel.subscribe(window.ship, 'metadata-pull-hook', `/preview${group}`,
(err) => {
console.error(err);
reject(err);
tempChannel.delete();
},
(ev: any) => {
if ('metadata-hook-update' in ev) {
done = true;
tempChannel.delete();
const upd = ev['metadata-hook-update'].preview as MetadataUpdatePreview;
resolve(upd);
} else {
done = true;
tempChannel.delete();
reject(new Error('no-permissions'));
}
},
(quit) => {
tempChannel.delete();
if(!done) {
reject(new Error('offline'));
}
},
(a) => {
console.log(a);
}
);
});
}
private metadataAction(data) {
return this.action('metadata-push-hook', 'metadata-update-1', data);
}
}

View File

@ -1,33 +0,0 @@
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class S3Api extends BaseApi<StoreState> {
setCurrentBucket(bucket: string) {
return this.s3Action({ 'set-current-bucket': bucket });
}
addBucket(bucket: string) {
return this.s3Action({ 'add-bucket': bucket });
}
removeBucket(bucket: string) {
return this.s3Action({ 'remove-bucket': bucket });
}
setEndpoint(endpoint: string) {
return this.s3Action({ 'set-endpoint': endpoint });
}
setAccessKeyId(accessKeyId: string) {
return this.s3Action({ 'set-access-key-id': accessKeyId });
}
setSecretAccessKey(secretAccessKey: string) {
return this.s3Action({ 'set-secret-access-key': secretAccessKey });
}
private s3Action(data: any) {
return this.action('s3-store', 's3-action', data);
}
}

View File

@ -1,73 +0,0 @@
import {
Bucket, Key,
SettingsUpdate, Value
} from '@urbit/api/settings';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class SettingsApi extends BaseApi<StoreState> {
private storeAction(action: SettingsUpdate): Promise<any> {
return this.action('settings-store', 'settings-event', action);
}
putBucket(key: Key, bucket: Bucket) {
return this.storeAction({
'put-bucket': {
'bucket-key': key,
'bucket': bucket
}
});
}
delBucket(key: Key) {
return this.storeAction({
'del-bucket': {
'bucket-key': key
}
});
}
putEntry(buc: Key, key: Key, val: Value) {
return this.storeAction({
'put-entry': {
'bucket-key': buc,
'entry-key': key,
'value': val
}
});
}
delEntry(buc: Key, key: Key) {
return this.storeAction({
'put-entry': {
'bucket-key': buc,
'entry-key': key
}
});
}
async getAll() {
const { all } = await this.scry('settings-store', '/all');
this.store.handleEvent({ data:
{ 'settings-data': { all } }
});
}
async getBucket(bucket: Key) {
const data: Record<string, unknown> = await this.scry('settings-store', `/bucket/${bucket}`);
this.store.handleEvent({ data: { 'settings-data': {
'bucket-key': bucket,
'bucket': data.bucket
} } });
}
async getEntry(bucket: Key, entry: Key) {
const data: Record<string, unknown> = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
this.store.handleEvent({ data: { 'settings-data': {
'bucket-key': bucket,
'entry-key': entry,
'entry': data.entry
} } });
}
}

View File

@ -0,0 +1,25 @@
import airlock from '~/logic/api';
import _ from 'lodash';
import { fetchIsAllowed } from '@urbit/api';
export async function disallowedShipsForOurContact(
ships: string[]
): Promise<string[]> {
return _.compact(
await Promise.all(
ships.map(async (s) => {
const ship = `~${s}`;
if (s === window.ship) {
return null;
}
const allowed = await airlock.scry(fetchIsAllowed(
`~${window.ship}`,
'personal',
ship,
true
));
return allowed ? null : ship;
})
)
);
}

View File

@ -0,0 +1,16 @@
import { newApi } from './fakeApi';
describe('API shim', () => {
it('should allow deep accesses', () => {
const api = newApi();
expect(api.foo.bar.baz.toString()).toBe('[fakeApi]');
});
it('should return promise on call', () => {
const api = newApi();
const method = api.foo.bar.baz;
const res = method();
expect('then' in res).toBe(true);
});
});

View File

@ -0,0 +1,22 @@
export function newApi() {
const target = () => {};
const handler = {
apply: function (target, that, args) {
return Promise.resolve();
},
get: function (target, prop, receiver) {
const original = target[prop];
if (prop === 'toString') {
return () => '[fakeApi]';
} else if (typeof original === 'function') {
return target[prop].bind(target);
} else if (original) {
return target[prop];
}
return newApi();
}
};
return new Proxy(target, handler) as any;
}

View File

@ -0,0 +1,45 @@
import { Content, GraphNode, unixToDa } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer';
export const makeComment = (
author: string,
time: number,
parentIndex: string,
contents: Content[]
): [BigInteger, GraphNode] => {
const da = unixToDa(time);
const index = `${parentIndex}/${da.toString()}`;
const children = new BigIntOrderedMap<GraphNode>().gas([
[
bigInt.one,
{
post: {
index: `${index}/1`,
author,
'time-sent': time,
signatures: [],
contents: contents,
hash: null
},
children: new BigIntOrderedMap()
}
]
]);
return [
da,
{
post: {
index,
author,
'time-sent': time,
signatures: [],
contents: [],
hash: null
},
children
}
];
};

View File

@ -12,14 +12,10 @@
// intrinsic expiry. // intrinsic expiry.
// //
// //
import GlobalApi from '../api/global';
import useStorageState from '../state/storage'; import useStorageState from '../state/storage';
class GcpManager { class GcpManager {
#api: GlobalApi | null = null; configure() {
configure(api: GlobalApi) {
this.#api = api;
} }
#running = false; #running = false;
@ -30,10 +26,6 @@ class GcpManager {
console.warn('GcpManager already running'); console.warn('GcpManager already running');
return; return;
} }
if (!this.#api) {
console.error('GcpManager must have api set');
return;
}
this.#running = true; this.#running = true;
this.refreshLoop(); this.refreshLoop();
} }
@ -63,7 +55,7 @@ class GcpManager {
private refreshLoop() { private refreshLoop() {
if (!this.#configured) { if (!this.#configured) {
this.#api!.gcp.isConfigured() useStorageState.getState().gcp.isConfigured()
.then((configured) => { .then((configured) => {
if (configured === undefined) { if (configured === undefined) {
throw new Error('can\'t check whether GCP is configured?'); throw new Error('can\'t check whether GCP is configured?');
@ -82,7 +74,7 @@ class GcpManager {
}); });
return; return;
} }
this.#api!.gcp.getToken() useStorageState.getState().gcp.getToken()
.then(() => { .then(() => {
const token = useStorageState.getState().gcp.token; const token = useStorageState.getState().gcp.token;
if (token) { if (token) {

View File

@ -0,0 +1,27 @@
import { Graph } from '@urbit/api';
import { BigInteger } from 'big-integer';
import _ from 'lodash';
import useMetadataState from '~/logic/state/metadata';
export function getNodeFromGraph(graph: Graph, index: BigInteger[]) {
return _.reduce(
index.slice(1),
(acc, val) => {
return acc?.children?.get(val);
},
graph.get(index[0])
);
}
export function getPostRoute(
graph: string,
index: BigInteger[],
thread = false
) {
const association = useMetadataState.getState().associations.graph[graph];
const segment = thread ? 'thread' : 'replies';
return `/~landscape${association.group}/feed/${segment}/${index
.map(i => i.toString())
.join('/')}`;
}

View File

@ -1,68 +0,0 @@
import useLocalState from '~/logic/state/local';
import useSettingsState from '~/logic/state/settings';
import { BackgroundConfig, RemoteContentPolicy } from '~/types';
import GlobalApi from '../api/global';
const getBackgroundString = (bg: BackgroundConfig) => {
if (bg?.type === 'url') {
return bg.url;
} else if (bg?.type === 'color') {
return bg.color;
} else {
return '';
}
};
export function useMigrateSettings(api: GlobalApi) {
const local = useLocalState();
const { display, remoteContentPolicy, calm } = useSettingsState();
return async () => {
const promises: Promise<any>[] = [];
if (local.hideAvatars !== calm.hideAvatars) {
promises.push(
api.settings.putEntry('calm', 'hideAvatars', local.hideAvatars)
);
}
if (local.hideNicknames !== calm.hideNicknames) {
promises.push(
api.settings.putEntry('calm', 'hideNicknames', local.hideNicknames)
);
}
if (
local?.background?.type &&
display.background !== getBackgroundString(local.background)
) {
promises.push(
api.settings.putEntry(
'display',
'background',
getBackgroundString(local.background)
)
);
promises.push(
api.settings.putEntry(
'display',
'backgroundType',
local.background?.type
)
);
}
Object.keys(local.remoteContentPolicy).forEach((_key) => {
const key = _key as keyof RemoteContentPolicy;
const localVal = local.remoteContentPolicy[key];
if (localVal !== remoteContentPolicy[key]) {
promises.push(
api.settings.putEntry('remoteContentPolicy', key, localVal)
);
}
});
await Promise.all(promises);
localStorage.removeItem('localReducer');
};
}

View File

@ -10,8 +10,18 @@ const makeIndexes = () => new Map([
['other', []] ['other', []]
]); ]);
export interface OmniboxItem {
title: string;
link: string;
app: string;
host: string;
description: string;
shiftLink: string;
shiftDescription: string;
}
// result schematic // result schematic
const result = function(title, link, app, host, description = 'Open', shiftLink = null, shiftDescription = null) { const result = function(title: string, link: string, app: string, host: string, description = 'Open', shiftLink = null as string | null, shiftDescription = null as string | null): OmniboxItem {
return { return {
'title': title, 'title': title,
'link': link, 'link': link,
@ -93,7 +103,7 @@ const otherIndex = function(config) {
return other; return other;
}; };
export default function index(contacts, associations, apps, currentGroup, groups, hide) { export default function index(contacts, associations, apps, currentGroup, groups, hide): Map<string, OmniboxItem[]> {
const indexes = makeIndexes(); const indexes = makeIndexes();
indexes.set('ships', shipIndex(contacts)); indexes.set('ships', shipIndex(contacts));
// all metadata from all apps is indexed // all metadata from all apps is indexed
@ -117,7 +127,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
let app = each['app-name']; let app = each['app-name'];
if (each['app-name'] === 'contacts') { if (each['app-name'] === 'contacts') {
app = 'groups'; app = 'groups';
}; }
if (each['app-name'] === 'graph') { if (each['app-name'] === 'graph') {
app = each.metadata.config.graph; app = each.metadata.config.graph;
@ -159,4 +169,4 @@ export default function index(contacts, associations, apps, currentGroup, groups
indexes.set('other', otherIndex(hide)); indexes.set('other', otherIndex(hide));
return indexes; return indexes;
}; }

View File

@ -0,0 +1,38 @@
export type SuspendState = 'result' | 'error' | 'pending';
export interface Suspender<T> {
read: () => T;
}
export function suspend<T>(awaiting: Promise<T>): Suspender<T> {
let state: SuspendState = 'pending';
let result: T | null = null;
const promise = awaiting
.then((res) => {
state = 'result';
result = res;
})
.catch((e) => {
state = 'error';
result = e;
});
return {
read: () => {
if (state === 'result') {
return result!;
} else if (state === 'error') {
throw result;
} else {
throw promise;
}
}
};
}
export function suspendWithResult<T>(result: T): Suspender<T> {
return {
read: () => result
};
}

View File

@ -5,7 +5,7 @@ const URL_REGEX = new RegExp(String(/^([^[\]]*?)(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?
const PATP_REGEX = /^([\s\S]*?)(~[a-z_-]+)([\s\S]*)/; const PATP_REGEX = /^([\s\S]*?)(~[a-z_-]+)([\s\S]*)/;
const GROUP_REGEX = new RegExp(String(/^([\s\S ]*?)(~[-a-z_]+\/[-a-z]+)([\s\S]*)/.source)); const GROUP_REGEX = new RegExp(String(/^([\s\S ]*?)(~[-a-z_]+\/[-a-z0-9]+)([\s\S]*)/.source));
const convertToGroupRef = group => `web+urbitgraph://group/${group}`; const convertToGroupRef = group => `web+urbitgraph://group/${group}`;
@ -33,9 +33,10 @@ const raceRegexes = (str) => {
content = { url: link[2] }; content = { url: link[2] };
} }
} }
if(groupRef && groupRef[1].length < pfix?.length) { const perma = parsePermalink(convertToGroupRef(groupRef?.[2]));
const [,,host] = perma?.group.split('/') ?? [];
if(groupRef && groupRef[1].length < pfix?.length && Boolean(perma) && urbitOb.isValidPatp(host)) {
pfix = groupRef[1]; pfix = groupRef[1];
const perma = parsePermalink(convertToGroupRef(groupRef[2]));
content = permalinkToReference(perma); content = permalinkToReference(perma);
sfix = groupRef[3]; sfix = groupRef[3];
} }

View File

@ -113,4 +113,19 @@ describe('tokenizeMessage', () => {
expect(text).toBe('. foo'); expect(text).toBe('. foo');
expect(url).toBe('https://tlon.io/test'); expect(url).toBe('https://tlon.io/test');
}); });
it('should ignore malformed group links', () => {
const example = 'test ~zoid/fakegroup';
const [{ text }, ...rest] = tokenizeMessage(example);
expect(text).toBe(example);
expect(rest.length).toBe(0);
});
it('should handle groups with numbers', () => {
const example = 'oh no, ~sampel/group-123-abc';
const [{ text }, { reference }] = tokenizeMessage(example);
expect(text).toBe('oh no, ');
expect(reference.group).toBe('/ship/~sampel/group-123-abc');
});
}); });

View File

@ -1,4 +1,4 @@
import { DragEvent, useCallback, useEffect, useState } from 'react'; import { DragEvent, useCallback, useEffect, useState, useMemo } from 'react';
function validateDragEvent(e: DragEvent): FileList | File[] | true | null { function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
const files: File[] = []; const files: File[] = [];
@ -43,7 +43,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
} }
setDragging(true); setDragging(true);
}, },
[setDragging] []
); );
const onDrop = useCallback( const onDrop = useCallback(
@ -56,7 +56,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
e.preventDefault(); e.preventDefault();
dragged(files, e); dragged(files, e);
}, },
[setDragging, dragged] [dragged]
); );
const onDragOver = useCallback( const onDragOver = useCallback(
@ -77,7 +77,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
setDragging(false); setDragging(false);
} }
}, },
[setDragging] []
); );
useEffect(() => { useEffect(() => {
@ -92,12 +92,12 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
}; };
}, []); }, []);
const bind = { const bind = useMemo(() => ({
onDragLeave, onDragLeave,
onDragOver, onDragOver,
onDrop, onDrop,
onDragEnter onDragEnter
}; }), [onDragEnter, onDragOver, onDrop, onDragEnter]);
return { bind, dragging }; return useMemo(() => ({ bind, dragging }), [bind, dragging]);
} }

View File

@ -0,0 +1,77 @@
import _ from 'lodash';
import { useState, ClipboardEvent } from 'react';
import { useFileDrag } from './useDrag';
import useStorage, { IuseStorage } from './useStorage';
export type FileUploadSource = 'drag' | 'paste' | 'direct';
interface FileUploadEventHandlers {
onSuccess: (url: string, source: FileUploadSource) => void;
onError?: (error: Error) => void;
}
interface FileUploadHandler {
onFiles: (
files: FileList | File[],
storage?: IuseStorage,
uploadSource?: FileUploadSource
) => void | Promise<void>;
}
function isFileUploadHandler(obj: any): obj is FileUploadHandler {
return typeof obj.onFiles === 'function';
}
type useFileUploadParams = {
multiple?: boolean;
} & (FileUploadEventHandlers | FileUploadHandler)
export function useFileUpload({ multiple = true, ...params }: useFileUploadParams) {
const storage = useStorage();
const {
canUpload, uploadDefault
} = storage;
const [source, setSource] = useState<FileUploadSource>('paste');
const drag = useFileDrag(f => uploadFiles(f, 'drag'));
function onPaste(event: ClipboardEvent) {
if (!event.clipboardData || !event.clipboardData.files.length) {
return;
}
event.preventDefault();
event.stopPropagation();
uploadFiles(event.clipboardData.files, 'paste');
}
function uploadFiles(files: FileList | File[], uploadSource: FileUploadSource) {
if (isFileUploadHandler(params)) {
return params.onFiles(files, storage, uploadSource);
}
if (!canUpload) {
return;
}
setSource(uploadSource);
const { onSuccess, onError } = params as FileUploadEventHandlers;
const fileArray = Array.from(files);
const toUpload = multiple ? fileArray : _.take(fileArray);
toUpload.forEach((file) => {
uploadDefault(file)
.then(url => onSuccess(url, source))
.catch((err: Error) => {
console.log(err);
onError && onError(err);
});
});
}
return {
...storage,
onPaste,
drag
};
}

View File

@ -36,10 +36,10 @@ export function useLazyScroll(
}; };
useEffect(() => { useEffect(() => {
if((oldCount > count) && ref.current) { if((oldCount > count) && ref.current && !isLoading) {
loadUntil(ref.current); loadUntil(ref.current);
} }
}, [count]); }, [count, isLoading]);
useEffect(() => { useEffect(() => {
if(!ready) { if(!ready) {
@ -48,7 +48,7 @@ export function useLazyScroll(
}, [ready]); }, [ready]);
useEffect(() => { useEffect(() => {
if (!ref.current || isDone || !ready) { if (!ref.current || isDone || !ready || isLoading) {
return; return;
} }
const scroll = ref.current; const scroll = ref.current;
@ -64,7 +64,7 @@ export function useLazyScroll(
return () => { return () => {
ref.current?.removeEventListener('scroll', onScroll); ref.current?.removeEventListener('scroll', onScroll);
}; };
}, [ref?.current, ready, isDone]); }, [ref?.current, ready, isDone, isLoading]);
return { isDone, isLoading }; return { isDone, isLoading };
} }

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react'; import { useMemo, useEffect, useState } from 'react';
function retrieve<T>(key: string, initial: T): T { export function retrieve<T>(key: string, initial: T): T {
const s = localStorage.getItem(key); const s = localStorage.getItem(key);
if (s) { if (s) {
try { try {
@ -12,26 +12,16 @@ function retrieve<T>(key: string, initial: T): T {
return initial; return initial;
} }
interface SetStateFunc<T> {
(t: T): T;
}
// See microsoft/typescript#37663 for filed bug
type SetState<T> = T extends any ? SetStateFunc<T> : never;
export function useLocalStorageState<T>(key: string, initial: T): any { export function useLocalStorageState<T>(key: string, initial: T): any {
const [state, _setState] = useState(() => retrieve(key, initial)); const [state, setState] = useState(() => retrieve(key, initial));
useEffect(() => { useEffect(() => {
_setState(retrieve(key, initial)); setState(retrieve(key, initial));
}, [key]); }, [key]);
const setState = useCallback( useEffect(() => {
(s: SetState<T>) => { localStorage.setItem(key, JSON.stringify(state));
const updated = typeof s === 'function' ? s(state) : s; }, [state]);
_setState(updated);
localStorage.setItem(key, JSON.stringify(updated));
},
[_setState, key, state]
);
return [state, setState] as const; return useMemo(() => [state, setState] as const, [state, setState]);
} }

View File

@ -0,0 +1,29 @@
import { useEffect, useMemo, useRef } from 'react';
import _ from 'lodash';
export function useResize<T extends HTMLElement>(
callback: (entry: ResizeObserverEntry, observer: ResizeObserver) => void
) {
const ref = useRef<T>();
useEffect(() => {
function observer(
entries: ResizeObserverEntry[],
observer: ResizeObserver
) {
for (const entry of _.flatten(entries)) {
callback(entry, observer);
}
}
const resizeObs = new ResizeObserver(observer);
resizeObs.observe(ref.current, { box: 'border-box' });
return () => {
resizeObs.unobserve(ref.current);
};
}, [callback]);
const bind = useMemo(() => ({ ref }), [ref]);
return bind;
}

View File

@ -0,0 +1,34 @@
import { useField } from 'formik';
import { MutableRefObject, useCallback, useMemo } from 'react';
import useStorage from './useStorage';
export function useUrlField(
id: string,
ref: MutableRefObject<HTMLInputElement>
) {
const [field, meta, helpers] = useField(id);
const { setValue, setError } = helpers;
const storage = useStorage();
const { uploadDefault, canUpload } = storage;
const onImageUpload = useCallback(async () => {
const file = ref.current?.files?.item(0);
if (!file || !canUpload) {
return;
}
try {
const url = await uploadDefault(file);
setValue(url);
} catch (e) {
setError(e.message);
}
}, [ref.current, uploadDefault, canUpload, setValue]);
const extStorage = useMemo(() => ({ ...storage, onImageUpload }), [
storage,
onImageUpload
]);
return [field, meta, helpers, extStorage] as const;
}

View File

@ -523,3 +523,32 @@ export const favicon = () => {
}); });
return svg; return svg;
}; };
export function binaryIndexOf(arr: BigInteger[], target: BigInteger): number | undefined {
let leftBound = 0;
let rightBound = arr.length - 1;
while(leftBound <= rightBound) {
const halfway = Math.floor((leftBound + rightBound) / 2);
if(arr[halfway].greater(target)) {
leftBound = halfway + 1;
} else if (arr[halfway].lesser(target)) {
rightBound = halfway - 1;
} else {
return halfway;
}
}
return undefined;
}
export async function jsonFetch<T>(info: RequestInfo, init?: RequestInit): Promise<T> {
const res = await fetch(info, init);
if(!res.ok) {
throw new Error('Bad Fetch Response');
}
const data = await res.json();
return data as T;
}
export function clone<T>(a: T) {
return JSON.parse(JSON.stringify(a)) as T;
}

View File

@ -1,13 +0,0 @@
import { Cage } from '~/types/cage';
import { StoreState } from '../store/type';
type LocalState = Pick<StoreState, 'connection'>;
export default class ConnectionReducer<S extends LocalState> {
reduce(json: Cage, state: S) {
if('connection' in json && json.connection) {
console.log(`Conn: ${json.connection}`);
state.connection = json.connection;
}
}
}

View File

@ -1,7 +1,9 @@
import { ContactUpdate, deSig } from '@urbit/api'; import { ContactUpdate, deSig } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { reduceState } from '../state/base'; import { BaseState } from '../state/base';
import useContactState, { ContactState } from '../state/contact'; import { ContactState as State } from '../state/contact';
type ContactState = State & BaseState<State>;
const initial = (json: ContactUpdate, state: ContactState): ContactState => { const initial = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'initial', false); const data = _.get(json, 'initial', false);
@ -71,23 +73,18 @@ const setPublic = (json: ContactUpdate, state: ContactState): ContactState => {
return state; return state;
}; };
export const ContactReducer = (json) => { export const reduceNacks = (json, state: ContactState): ContactState => {
const data: ContactUpdate = _.get(json, 'contact-update', false); const data = json?.resource;
if (data) { if(data) {
reduceState<ContactState, ContactUpdate>(useContactState, data, [ state.nackedContacts.add(`~${data.res}`);
}
return state;
};
export const reduce = [
initial, initial,
add, add,
remove, remove,
edit, edit,
setPublic setPublic
]); ];
}
// TODO: better isolation
const res = _.get(json, 'resource', false);
if (res) {
useContactState.setState({
nackedContacts: useContactState.getState().nackedContacts.add(`~${res.ship}`)
});
}
};

View File

@ -1,32 +0,0 @@
import type { Cage } from '~/types/cage';
import type { GcpToken } from '../../types/gcp-state';
import { reduceState } from '../state/base';
import useStorageState, { StorageState } from '../state/storage';
export default class GcpReducer {
reduce(json: Cage) {
reduceState<StorageState, any>(useStorageState, json, [
reduceToken
]);
}
}
const reduceToken = (json: Cage, state: StorageState): StorageState => {
const data = json['gcp-token'];
if (data) {
setToken(data, state);
}
return state;
};
const setToken = (data: any, state: StorageState): StorageState => {
if (isToken(data)) {
state.gcp.token = data;
}
return state;
};
const isToken = (token: any): token is GcpToken => {
return (typeof(token.accessKey) === 'string' &&
typeof(token.expiresIn) === 'number');
};

View File

@ -7,8 +7,13 @@ import BigIntArrayOrderedMap, {
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import produce from 'immer'; import produce from 'immer';
import _ from 'lodash'; import _ from 'lodash';
import { reduceState } from '../state/base'; import { BaseState, reduceState } from '../state/base';
import useGraphState, { GraphState } from '../state/graph'; import useGraphState, { GraphState as State } from '../state/graph';
/* eslint-disable camelcase */
import { unstable_batchedUpdates } from 'react-dom';
type GraphState = State & BaseState<State>;
const mapifyChildren = (children) => { const mapifyChildren = (children) => {
return new BigIntOrderedMap().gas( return new BigIntOrderedMap().gas(
@ -245,9 +250,6 @@ export const addNodes = (json, state) => {
post, post,
resource resource
) => { ) => {
if (!post.hash) {
return [graph, flatGraph, threadGraphs];
}
const timestamp = post['time-sent']; const timestamp = post['time-sent'];
if (state.graphTimesentMap[resource][timestamp]) { if (state.graphTimesentMap[resource][timestamp]) {
@ -403,26 +405,17 @@ export const addNodes = (json, state) => {
const removePosts = (json, state: GraphState): GraphState => { const removePosts = (json, state: GraphState): GraphState => {
const _remove = (graph, index) => { const _remove = (graph, index) => {
const child = graph.get(index[0]); const child = graph.get(index[0]);
if(!child) {
return graph;
}
if (index.length === 1) { if (index.length === 1) {
if (child) {
return graph.set(index[0], { return graph.set(index[0], {
post: child.post.hash || '', post: child.post.hash || '',
children: child.children children: child.children
}); });
}
} else { } else {
if (child) { const node = { ...child, children: _remove(child.children, index.slice(1)) };
_remove(child.children, index.slice(1)); return graph.set(index[0], node);
return graph.set(index[0], child);
} else {
const child = graph.get(index[0]);
if (child) {
return graph.set(index[0], produce((draft: any) => {
draft.children = _remove(draft.children, index.slice(1));
}));
}
return graph;
}
} }
}; };
@ -445,9 +438,16 @@ const removePosts = (json, state: GraphState): GraphState => {
return state; return state;
}; };
export const reduceDm = [
acceptOrRejectDm,
pendings,
setScreen
];
export const GraphReducer = (json) => { export const GraphReducer = (json) => {
const data = _.get(json, 'graph-update', false); const data = _.get(json, 'graph-update', false);
unstable_batchedUpdates(() => {
if (data) { if (data) {
reduceState<GraphState, any>(useGraphState, data, [ reduceState<GraphState, any>(useGraphState, data, [
keys, keys,
@ -471,13 +471,5 @@ export const GraphReducer = (json) => {
if (thread) { if (thread) {
reduceState<GraphState, any>(useGraphState, thread, [addNodesThread]); reduceState<GraphState, any>(useGraphState, thread, [addNodesThread]);
} }
const dm = _.get(json, 'dm-hook-action', false); });
if(dm) {
console.log(dm);
reduceState<GraphState, any>(useGraphState, dm, [
acceptOrRejectDm,
pendings,
setScreen
]);
}
}; };

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