mirror of
https://github.com/urbit/shrub.git
synced 2024-12-25 04:52:06 +03:00
merge master; loosen :hood's updates
This commit is contained in:
commit
616ab66f26
3
.eslintrc.js
Normal file
3
.eslintrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
ignorePatterns: ["**/*"]
|
||||
};
|
2
.github/actions/glob/Dockerfile
vendored
2
.github/actions/glob/Dockerfile
vendored
@ -1,4 +1,4 @@
|
||||
FROM jaredtobin/janeway:v0.15.3
|
||||
FROM tloncorp/janeway:v0.15.4
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
EXPOSE 22/tcp
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: cd 'pkg/interface' && npm i
|
||||
- run: npm i && npm run bootstrap
|
||||
- name: Publish to Chromatic
|
||||
uses: chromaui/action@v1
|
||||
with:
|
||||
|
24
.github/workflows/frontend-test.yml
vendored
Normal file
24
.github/workflows/frontend-test.yml
vendored
Normal 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
|
14
.github/workflows/typescript-check.yml
vendored
14
.github/workflows/typescript-check.yml
vendored
@ -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
1
.gitignore
vendored
@ -33,6 +33,7 @@ result-*
|
||||
|
||||
# NodeJS
|
||||
node_modules
|
||||
.eslintcache
|
||||
|
||||
# Haskell
|
||||
.stack-work
|
||||
|
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
_
|
8
.husky/pre-commit
Executable file
8
.husky/pre-commit
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
command -v npx > /dev/null || {
|
||||
exit 0
|
||||
}
|
||||
|
||||
npx lint-staged
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7293a04c2de520a04f1a1afb49e497a5465b39a5863bee39d02e5b3ba491193a
|
||||
size 13618239
|
||||
oid sha256:42b81ff30114d982a485d851879bd3f4abbfaa6272351eaca717e10370af293e
|
||||
size 13645079
|
||||
|
8
lerna.json
Normal file
8
lerna.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"packages": [
|
||||
"pkg/npm/*",
|
||||
"pkg/btc-wallet",
|
||||
"pkg/interface"
|
||||
],
|
||||
"version": "independent"
|
||||
}
|
6915
package-lock.json
generated
Normal file
6915
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal 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"
|
||||
}
|
||||
}
|
@ -1,25 +1,10 @@
|
||||
/+ default-agent
|
||||
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|
||||
|%
|
||||
+$ state
|
||||
$: %16
|
||||
drum=state:drum
|
||||
helm=state:helm
|
||||
kiln=state:kiln
|
||||
==
|
||||
+$ state [%16 any-state-tuple]
|
||||
+$ any-state
|
||||
$% state
|
||||
[ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
|
||||
[%7 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%8 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%9 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%10 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%11 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%12 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%13 drum=state-2:drum helm=state:helm kiln=state-1:kiln]
|
||||
[%14 drum=state-3:drum helm=state:helm kiln=state-1:kiln]
|
||||
[%15 drum=state-3:drum helm=state:helm kiln=state-2:kiln]
|
||||
[%16 drum=state-4:drum helm=state:helm kiln=state-3:kiln]
|
||||
$% [ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
|
||||
[ver=?(%7 %8 %9 %10 %11 %12 %13 %14 %15 %16) any-state-tuple]
|
||||
==
|
||||
+$ any-state-tuple
|
||||
$: drum=any-state:drum
|
||||
|
@ -25,8 +25,6 @@
|
||||
^- (list @tas)
|
||||
:~ %group-store
|
||||
%metadata-store
|
||||
%contact-store
|
||||
%contact-hook
|
||||
%invite-store
|
||||
%graph-store
|
||||
==
|
||||
|
433
pkg/arvo/app/notify.hoon
Normal file
433
pkg/arvo/app/notify.hoon
Normal file
@ -0,0 +1,433 @@
|
||||
::
|
||||
/- *notify, resource, hark-store, post
|
||||
/+ default-agent, verb, dbug, group, agentio
|
||||
::
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
+$ provider-state (map term provider-entry)
|
||||
+$ provider-entry
|
||||
$: notify-endpoint=@t
|
||||
binding-endpoint=@t
|
||||
auth-token=@t
|
||||
clients=(map ship binding=(unit @t))
|
||||
=whitelist
|
||||
==
|
||||
::
|
||||
+$ client-state
|
||||
$: providers=(jug @p term)
|
||||
==
|
||||
::
|
||||
+$ state-0
|
||||
$: %0
|
||||
=provider-state
|
||||
=client-state
|
||||
==
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
==
|
||||
::
|
||||
--
|
||||
::
|
||||
=| state-0
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
::
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
do ~(. +> bowl)
|
||||
io ~(. agentio bowl)
|
||||
pass pass:io
|
||||
::
|
||||
++ on-init
|
||||
:_ this
|
||||
[(~(watch-our pass:io /hark) %hark-store /updates)]~
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= =old=vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(versioned-state old-vase)
|
||||
?- -.old
|
||||
%0
|
||||
:_ this(state old)
|
||||
?. (~(has by wex.bowl) [/hark our.bowl %hark-store])
|
||||
~
|
||||
[(~(watch-our pass:io /hark) %hark-store /updates)]~
|
||||
==
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
|^
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%notify-provider-action (handle-provider-action !<(provider-action vase))
|
||||
%notify-client-action (handle-client-action !<(client-action vase))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ handle-provider-action
|
||||
|= act=provider-action
|
||||
^- (quip card _state)
|
||||
?- -.act
|
||||
%add
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=/ new-entry=provider-entry
|
||||
:* notify.act
|
||||
binding.act
|
||||
auth-token.act
|
||||
~
|
||||
whitelist.act
|
||||
==
|
||||
[~ state(provider-state (~(put by provider-state) service.act new-entry))]
|
||||
::
|
||||
%remove
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=/ entry=(unit provider-entry) (~(get by provider-state) service.act)
|
||||
?~ entry
|
||||
~|("no such service: {<service.act>}" !!)
|
||||
:_ state(provider-state (~(del by provider-state) service.act))
|
||||
%+ turn ~(tap by clients.u.entry)
|
||||
|= [who=@p *]
|
||||
^- card
|
||||
(leave-path:pass [who %notify] /notify/(scot %p who)/[service.act])
|
||||
::
|
||||
%client-join
|
||||
=/ entry=(unit provider-entry) (~(get by provider-state) service.act)
|
||||
?~ entry
|
||||
~|("no such service: {<service.act>}" !!)
|
||||
?. (is-whitelisted:do src.bowl u.entry)
|
||||
~|("permission denied" !!)
|
||||
=. clients.u.entry (~(put by clients.u.entry) src.bowl ~)
|
||||
:_ state(provider-state (~(put by provider-state) service.act u.entry))
|
||||
:~ %: register-binding:do
|
||||
service.act
|
||||
u.entry
|
||||
binding-endpoint.u.entry
|
||||
src.bowl
|
||||
address.act
|
||||
==
|
||||
%+ watch:pass
|
||||
[src.bowl %notify]
|
||||
/notify/(scot %p src.bowl)/[service.act]
|
||||
==
|
||||
::
|
||||
%client-leave
|
||||
=/ entry=(unit provider-entry) (~(get by provider-state) service.act)
|
||||
?~ entry
|
||||
~|("no such service: {<service.act>}" !!)
|
||||
?. (is-client:do src.bowl u.entry)
|
||||
~|("permission denied" !!)
|
||||
=/ client-info=(unit @t) (~(got by clients.u.entry) src.bowl)
|
||||
=. clients.u.entry (~(del by clients.u.entry) src.bowl)
|
||||
:_ state(provider-state (~(put by provider-state) service.act u.entry))
|
||||
?~ client-info
|
||||
:_ ~
|
||||
%+ leave-path:pass
|
||||
[src.bowl %notify]
|
||||
/notify/(scot %p src.bowl)/[service.act]
|
||||
:~ %: remove-binding:do
|
||||
service.act
|
||||
u.entry
|
||||
src.bowl
|
||||
binding-endpoint.u.entry
|
||||
u.client-info
|
||||
==
|
||||
%+ leave-path:pass
|
||||
[src.bowl %notify]
|
||||
/notify/(scot %p src.bowl)/[service.act]
|
||||
==
|
||||
==
|
||||
::
|
||||
++ handle-client-action
|
||||
|= act=client-action
|
||||
^- (quip card _state)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?- -.act
|
||||
%connect-provider
|
||||
=. providers.client-state
|
||||
(~(put ju providers.client-state) who.act service.act)
|
||||
=/ pact=provider-action [%client-join service.act address.act]
|
||||
:_ state
|
||||
[(poke:pass [who.act %notify] %notify-provider-action !>(pact))]~
|
||||
::
|
||||
%remove-provider
|
||||
=. providers.client-state
|
||||
(~(del ju providers.client-state) who.act service.act)
|
||||
=/ pact=provider-action [%client-leave service.act]
|
||||
:_ state
|
||||
[(poke:pass [who.act %notify] %notify-provider-action !>(pact))]~
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?+ path (on-watch:def path)
|
||||
[%notify @ @ ~]
|
||||
=* service i.t.t.path
|
||||
?. (~(has ju providers.client-state) src.bowl service)
|
||||
~|("permission denied" !!)
|
||||
`this
|
||||
==
|
||||
::
|
||||
++ on-leave
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
`this
|
||||
::
|
||||
++ on-peek on-peek:def
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?+ wire (on-agent:def wire sign)
|
||||
::
|
||||
:: subscription from client to their own hark-store
|
||||
::
|
||||
[%hark ~]
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%fact
|
||||
:_ this
|
||||
?. ?=(%hark-update p.cage.sign)
|
||||
~
|
||||
=+ !<(hark-update=update:hark-store q.cage.sign)
|
||||
=/ notes=(list notification) (filter-notifications:do hark-update)
|
||||
?~ notes
|
||||
~
|
||||
:: only send the last one, since hark accumulates notifcations
|
||||
=/ =update [%notification `notification`(snag 0 (flop notes))]
|
||||
=/ card (fact-all:io %notify-update !>(update))
|
||||
(drop card)
|
||||
::
|
||||
%kick
|
||||
:_ this
|
||||
[%pass /hark %agent [our.bowl %hark-store] %watch /updates]~
|
||||
==
|
||||
::
|
||||
:: subscription from provider to client
|
||||
::
|
||||
[%agentio-watch %notify @ @ ~]
|
||||
=/ who (slav %p i.t.t.wire)
|
||||
=* service i.t.t.t.wire
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%fact
|
||||
?> ?=(%notify-update p.cage.sign)
|
||||
=+ !<(=update q.cage.sign)
|
||||
:_ this
|
||||
?- -.update
|
||||
%notification
|
||||
=/ entry=(unit provider-entry) (~(get by provider-state) service)
|
||||
?~ entry
|
||||
~
|
||||
[(send-notification:do u.entry who notification.update)]~
|
||||
==
|
||||
::
|
||||
%kick
|
||||
:_ this
|
||||
[(watch:pass [who %notify] /notify/(scot %p who)/[service])]~
|
||||
::
|
||||
%watch-ack
|
||||
?~ p.sign
|
||||
`this
|
||||
((slog u.p.sign) `this)
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
?+ wire (on-arvo:def wire sign-arvo)
|
||||
[%register-binding @ @ @ ~]
|
||||
=/ who=@p (slav %p i.t.wire)
|
||||
=* service i.t.t.wire
|
||||
::
|
||||
?> ?=(%iris -.sign-arvo)
|
||||
?> ?=(%http-response +<.sign-arvo)
|
||||
?> ?=(%finished -.client-response.sign-arvo)
|
||||
?> ?=(^ full-file.client-response.sign-arvo)
|
||||
=/ =mime-data:iris u.full-file.client-response.sign-arvo
|
||||
?> =('application/json' type.mime-data)
|
||||
=/ jon=json
|
||||
(fall (rush (@t q.data.mime-data) apex:de-json:html) *json)
|
||||
=/ [sid=@t message=@t]
|
||||
%. jon
|
||||
%- ot:dejs:format
|
||||
:~ sid+so:dejs:format
|
||||
message+so:dejs:format
|
||||
==
|
||||
::
|
||||
=/ entry=(unit provider-entry) (~(get by provider-state) service)
|
||||
:- ~
|
||||
?~ entry
|
||||
this
|
||||
=. clients.u.entry (~(put by clients.u.entry) who `sid)
|
||||
this(provider-state (~(put by provider-state) service u.entry))
|
||||
::
|
||||
[%remove-binding *]
|
||||
`this
|
||||
::
|
||||
[%send-notification *]
|
||||
`this
|
||||
==
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|_ bowl=bowl:gall
|
||||
::
|
||||
++ filter-notifications
|
||||
|= =update:hark-store
|
||||
^- (list notification)
|
||||
?+ -.update ~
|
||||
%more
|
||||
(zing (turn more.update filter-notifications))
|
||||
::
|
||||
%added
|
||||
?- -.index.update
|
||||
%graph
|
||||
?: =(`%graph-validator-dm mark.index.update)
|
||||
?. ?=(%graph -.contents.notification.update)
|
||||
~
|
||||
%+ turn list.contents.notification.update
|
||||
|= =post:post
|
||||
^- notification
|
||||
[graph.index.update index.post]
|
||||
?: =(`%graph-validator-chat mark.index.update)
|
||||
=/ hid (group-is-hidden graph.index.update)
|
||||
?~ hid ~
|
||||
?. u.hid ~
|
||||
?. ?=(%graph -.contents.notification.update)
|
||||
~
|
||||
%+ turn list.contents.notification.update
|
||||
|= =post:post
|
||||
^- notification
|
||||
[graph.index.update index.post]
|
||||
~
|
||||
::
|
||||
%group ~
|
||||
==
|
||||
==
|
||||
::
|
||||
++ group-is-hidden
|
||||
|= =resource:resource
|
||||
^- (unit ?)
|
||||
=/ grp=(unit group:group) (~(scry-group group bowl) resource)
|
||||
?~ grp ~
|
||||
`hidden.u.grp
|
||||
::
|
||||
++ is-whitelisted
|
||||
|= [who=@p entry=provider-entry]
|
||||
^- ?
|
||||
|^
|
||||
?| public.whitelist.entry
|
||||
=(our.bowl who)
|
||||
is-kid
|
||||
(~(has in users.whitelist.entry) who)
|
||||
in-group
|
||||
==
|
||||
::
|
||||
++ is-kid
|
||||
?& kids.whitelist.entry
|
||||
=(our.bowl (sein:title our.bowl now.bowl who))
|
||||
==
|
||||
::
|
||||
++ in-group
|
||||
=/ gs ~(tap in groups.whitelist.entry)
|
||||
|-
|
||||
?~ gs %.n
|
||||
?: (~(is-member group bowl) who i.gs)
|
||||
%.y
|
||||
$(gs t.gs)
|
||||
--
|
||||
::
|
||||
++ is-client
|
||||
|= [who=@p entry=provider-entry]
|
||||
^- ?
|
||||
(~(has by clients.entry) who)
|
||||
::
|
||||
++ post-form
|
||||
|= [=wire url=@t auth=@t params=(list [@t @t])]
|
||||
^- card
|
||||
=/ data
|
||||
%+ roll
|
||||
%+ sort params
|
||||
|= [[p=@t @t] [q=@t @t]]
|
||||
(aor p q)
|
||||
|= [[p=@t q=@t] out=_url]
|
||||
(rap 3 out p q ~)
|
||||
=/ hmac-sig (hmac-sha1t:hmac:crypto auth data)
|
||||
=/ b64-sig (en:base64:mimes:html (met 3 hmac-sig) (swp 3 hmac-sig))
|
||||
=/ headers
|
||||
:~ ['X-Twilio-Signature' b64-sig]
|
||||
['Content-Type' 'application/x-www-form-urlencoded']
|
||||
==
|
||||
=/ form-data (build-form-data params)
|
||||
=/ =request:http
|
||||
[%'POST' url headers `[(met 3 form-data) form-data]]
|
||||
[%pass wire %arvo %i %request request *outbound-config:iris]
|
||||
::
|
||||
++ build-form-data
|
||||
|= data=(list [@t @t])
|
||||
^- @t
|
||||
%+ roll data
|
||||
|= [[p=@t q=@t] out=@t]
|
||||
?: =(out '')
|
||||
(rap 3 p '=' q ~)
|
||||
(rap 3 out '&' p '=' q ~)
|
||||
::
|
||||
++ send-notification
|
||||
|= [entry=provider-entry who=@p =notification]
|
||||
^- card
|
||||
=/ params=(list [@t @t])
|
||||
:~ identity+(rsh [3 1] (scot %p who))
|
||||
ship+(rsh [3 1] (scot %p entity.resource.notification))
|
||||
graph+name.resource.notification
|
||||
:- %node
|
||||
%+ roll index.notification
|
||||
|= [in=@ out=@t]
|
||||
(rap 3 out '/' (scot %ud in) ~)
|
||||
==
|
||||
%: post-form
|
||||
/send-notification/(scot %uv (sham eny.bowl))
|
||||
notify-endpoint.entry
|
||||
auth-token.entry
|
||||
params
|
||||
==
|
||||
::
|
||||
++ register-binding
|
||||
|= [service=term entry=provider-entry url=@t who=@p address=@t]
|
||||
^- card
|
||||
=/ params=(list [@t @t])
|
||||
:~ identity+(rsh [3 1] (scot %p who))
|
||||
bindingtype+'apn'
|
||||
address+address
|
||||
action+'add'
|
||||
==
|
||||
%: post-form
|
||||
/register-binding/(scot %p who)/[service]/(scot %uv (sham eny.bowl))
|
||||
binding-endpoint.entry
|
||||
auth-token.entry
|
||||
params
|
||||
==
|
||||
::
|
||||
++ remove-binding
|
||||
|= [service=term entry=provider-entry who=@p url=@t sid=@t]
|
||||
^- card
|
||||
=/ params=(list [@t @t])
|
||||
:~ sid+sid
|
||||
action+'remove'
|
||||
==
|
||||
%: post-form
|
||||
/remove-binding/(scot %p who)/[service]/(scot %uv (sham eny.bowl))
|
||||
binding-endpoint.entry
|
||||
auth-token.entry
|
||||
params
|
||||
==
|
||||
--
|
11
pkg/arvo/gen/group-view/join.hoon
Normal file
11
pkg/arvo/gen/group-view/join.hoon
Normal 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]
|
20
pkg/arvo/gen/hood/fuse-list.hoon
Normal file
20
pkg/arvo/gen/hood/fuse-list.hoon
Normal file
@ -0,0 +1,20 @@
|
||||
:: Kiln: Fuse local desk from (optionally-)foreign sources
|
||||
::
|
||||
:::: /hoon/fuse/hood/gen
|
||||
::
|
||||
/+ *hood-kiln
|
||||
/* help-text %txt /gen/hood/fuse/help/txt
|
||||
=, clay
|
||||
::
|
||||
::::
|
||||
::
|
||||
=>
|
||||
|%
|
||||
+$ fuse-list-arg $@(~ [des=desk ~])
|
||||
--
|
||||
:- %say
|
||||
|= [* [arg=fuse-list-arg] ~]
|
||||
:- %kiln-fuse-list
|
||||
?~ arg
|
||||
~
|
||||
`des.arg
|
@ -2,14 +2,59 @@
|
||||
::
|
||||
:::: /hoon/fuse/hood/gen
|
||||
::
|
||||
/+ *hood-kiln
|
||||
/* help-text %txt /gen/hood/fuse/help/txt
|
||||
=, clay
|
||||
::
|
||||
::::
|
||||
::
|
||||
=>
|
||||
|%
|
||||
+$ fuse-arg
|
||||
$: des=desk
|
||||
:: specified as [germ path] instead of [path germ] so
|
||||
:: users can write mate//=home= instead of [/=home= %mate]
|
||||
::
|
||||
res=[?([%cancel ~] [bas=path con=(list [germ path])])]
|
||||
==
|
||||
::
|
||||
++ parse-fuse-source
|
||||
|= bec=beak
|
||||
^- fuse-source
|
||||
:: This is a slight overload of the label, but
|
||||
:: it provides a nicer interface for the user so
|
||||
:: we'll go with it.
|
||||
::
|
||||
?: ?=([%tas *] r.bec)
|
||||
?: =(p.r.bec %track)
|
||||
[p.bec q.bec %trak]
|
||||
bec
|
||||
bec
|
||||
::
|
||||
++ de-beak
|
||||
|= pax=path
|
||||
^- beak
|
||||
=/ bem=beam (need (de-beam pax))
|
||||
?> =(s.bem /)
|
||||
-.bem
|
||||
::
|
||||
++ path-to-fuse-source
|
||||
|= pax=path
|
||||
^- fuse-source
|
||||
(parse-fuse-source (de-beak pax))
|
||||
--
|
||||
:- %say
|
||||
|= [[now=@da eny=@uvJ bec=beak] [arg=[?(~ [des=desk bas=beak con=(list [beak germ]) ~])]] ~]
|
||||
|= [* [arg=[?(~ fuse-arg)]] [overwrite=$~(| flag) ~]]
|
||||
:- %kiln-fuse
|
||||
?~ arg
|
||||
((slog (turn `wain`help-text |=(=@t leaf+(trip t)))) ~)
|
||||
[des bas con]:arg
|
||||
:- des.arg
|
||||
?: ?=([%cancel ~] res.arg)
|
||||
~
|
||||
:+ overwrite
|
||||
(path-to-fuse-source bas.res.arg)
|
||||
%+ turn
|
||||
con.res.arg
|
||||
|= [g=germ pax=path]
|
||||
^- [fuse-source germ]
|
||||
[(path-to-fuse-source pax) g]
|
||||
|
@ -1,8 +1,21 @@
|
||||
Usage:
|
||||
|
||||
|fuse %destination-desk base-beak ~[[source-beak %some-germ] [another-beak %another-germ]]
|
||||
|fuse %dest /=kids= mate//~nel/home= meet//~zod/kids/track
|
||||
|fuse %old-desk /=kids= only-that//~nus/test=, =overwrite &
|
||||
|fuse %desk-to-cancel-fuse-into %cancel
|
||||
|
||||
A fuse replaces the contents of %destination-desk with the merge of the
|
||||
specified beaks according to their merge strategies. This has no dependence
|
||||
on the previous state of %destination-desk so any commits/work there will
|
||||
be overwritten.
|
||||
A %fuse request in clay replaces the contents of %destination-desk
|
||||
with the merge of the specified beaks according to their merge
|
||||
strategies. This has no dependence on the previous state of %dest
|
||||
so any commits/work there will be overwritten.
|
||||
|
||||
|fuse extends this concept with the idea of a tracked source. When
|
||||
specifying beaks to include in your fuse, specify %track instead of
|
||||
a case. This will tell |fuse to retrieve the latest version of the
|
||||
source beak AND to rerun the %fuse request whenever that tracked
|
||||
source changes. A fuse can have many tracked sources, or none. The
|
||||
base may be tracked as well.
|
||||
|
||||
The overwrite flag allows you to overwrite a currently ongoing fuse.
|
||||
Without this flag, attempting a fuse into a desk that you already
|
||||
|fuse'd into will error.
|
||||
|
@ -115,6 +115,16 @@
|
||||
^- card
|
||||
[%give %fact paths cage]
|
||||
::
|
||||
++ fact-all
|
||||
|= =cage
|
||||
^- (unit card)
|
||||
=/ paths=(list path)
|
||||
%+ turn ~(tap by sup.bowl)
|
||||
|= [duct ship =path]
|
||||
path
|
||||
?~ paths ~
|
||||
`[%give %fact paths cage]
|
||||
::
|
||||
++ kick
|
||||
|= paths=(list path)
|
||||
[%give %kick paths ~]
|
||||
|
@ -1,5 +1,6 @@
|
||||
/- bc=bitcoin
|
||||
/+ bcu=bitcoin-utils
|
||||
~% %bip-158-top ..part ~
|
||||
|%
|
||||
++ params
|
||||
|%
|
||||
@ -8,6 +9,7 @@
|
||||
--
|
||||
::
|
||||
++ siphash
|
||||
~/ %siphash
|
||||
|= [k=byts m=byts]
|
||||
^- byts
|
||||
|^
|
||||
@ -86,25 +88,28 @@
|
||||
:: write appends to the back
|
||||
::
|
||||
++ str
|
||||
~% %str ..params ~
|
||||
|%
|
||||
++ read-bit
|
||||
~/ %read-bit
|
||||
|= s=bits:bc
|
||||
^- [bit=@ub rest=bits:bc]
|
||||
?> (gth wid.s 0)
|
||||
:* ?:((gth wid.s (met 0 dat.s)) 0b0 0b1)
|
||||
[(dec wid.s) (end [0 (dec wid.s)] dat.s)]
|
||||
==
|
||||
:+ ?:((gth wid.s (met 0 dat.s)) 0b0 0b1)
|
||||
(dec wid.s)
|
||||
(end [0 (dec wid.s)] dat.s)
|
||||
::
|
||||
::
|
||||
++ read-bits
|
||||
~/ %read-bits
|
||||
|= [n=@ s=bits:bc]
|
||||
^- [bits:bc rest=bits:bc]
|
||||
=| bs=bits:bc
|
||||
|-
|
||||
?: =(n 0) [bs s]
|
||||
=^ b s (read-bit s)
|
||||
$(n (dec n), bs (write-bits bs [1 b]))
|
||||
=/ r=@ (sub wid.s n)
|
||||
:- n^(cut 0 [r n] dat.s)
|
||||
r^(cut 0 [0 r] dat.s)
|
||||
::
|
||||
++ write-bits
|
||||
~/ %write-bits
|
||||
|= [s1=bits:bc s2=bits:bc]
|
||||
^- bits:bc
|
||||
[(add wid.s1 wid.s2) (can 0 ~[s2 s1])]
|
||||
@ -112,6 +117,7 @@
|
||||
:: +gol: Golomb-Rice encoding/decoding
|
||||
::
|
||||
++ gol
|
||||
~% %gol ..params ~
|
||||
|%
|
||||
:: +en: encode x and append to end of s
|
||||
:: - s: bits stream
|
||||
@ -119,6 +125,7 @@
|
||||
:: - p: golomb-rice p param
|
||||
::
|
||||
++ en
|
||||
~/ %en
|
||||
|= [s=bits:bc x=@ p=@]
|
||||
^- bits:bc
|
||||
=+ q=(rsh [0 p] x)
|
||||
@ -128,6 +135,7 @@
|
||||
(write-bits:str unary r)
|
||||
::
|
||||
++ de
|
||||
~/ %de
|
||||
|= [s=bits:bc p=@]
|
||||
^- [delta=@ rest=bits:bc]
|
||||
|^ ?> (gth wid.s 0)
|
||||
@ -148,6 +156,7 @@
|
||||
:: +hsh
|
||||
::
|
||||
++ hsh
|
||||
~% %hsh ..params ~
|
||||
|%
|
||||
:: +to-range
|
||||
:: - item: scriptpubkey to hash
|
||||
@ -155,9 +164,10 @@
|
||||
:: - k: key for siphash (end of blockhash, reversed)
|
||||
::
|
||||
++ to-range
|
||||
~/ %to-range
|
||||
|= [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
|
||||
@ -171,6 +181,7 @@
|
||||
--
|
||||
::
|
||||
++ parse-filter
|
||||
~/ %parse-filter
|
||||
|= filter=hexb:bc
|
||||
^- [n=@ux gcs-set=bits:bc]
|
||||
=/ n n:(de:csiz:bcu filter)
|
||||
@ -180,6 +191,7 @@
|
||||
:: +to-key: blockhash (little endian) to key for siphash
|
||||
::
|
||||
++ to-key
|
||||
~/ %to-key
|
||||
|= blockhash=tape
|
||||
^- byts
|
||||
%+ take:byt:bcu 16
|
||||
@ -191,6 +203,7 @@
|
||||
:: - targets: scriptpubkeys to match
|
||||
::
|
||||
++ match
|
||||
~/ %match
|
||||
|= [filter=hexb:bc k=byts targets=(list byts)]
|
||||
^- ?
|
||||
=/ [p=@ m=@] [p:params m:params]
|
||||
@ -214,6 +227,7 @@
|
||||
:: - targets: scriptpubkeys to match
|
||||
::
|
||||
++ all-match
|
||||
~/ %all-match
|
||||
|= [filter=hexb:bc blockhash=hexb:bc targets=(list [address:bc byts])]
|
||||
^- (set [address:bc hexb:bc])
|
||||
=/ k (to-key (trip (to-cord:hxb:bcu blockhash)))
|
||||
|
@ -1,12 +1,13 @@
|
||||
/- *sole
|
||||
/+ sole
|
||||
|%
|
||||
+$ state state-4
|
||||
+$ any-state
|
||||
$~ *state
|
||||
$% state-4
|
||||
state-3
|
||||
state-2
|
||||
==
|
||||
+$ state state-4
|
||||
+$ state-4 [%4 pith-4]
|
||||
+$ state-3 [%3 pith-3]
|
||||
+$ state-2 [%2 pith-2]
|
||||
@ -80,7 +81,8 @@
|
||||
--
|
||||
:: TODO: remove .ost
|
||||
::
|
||||
|= [hid=bowl:gall state]
|
||||
|= [hid=bowl:gall any-state]
|
||||
?> ?=(%4 +<+<)
|
||||
=* sat +<+
|
||||
=/ ost 0
|
||||
=+ (~(gut by bin) ost *source)
|
||||
@ -153,7 +155,7 @@
|
||||
?> ?=(%4 -.old)
|
||||
=. sat old
|
||||
=. dev (~(gut by bin) ost *source)
|
||||
..on-load
|
||||
this
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
|= [way=wire saw=(unit tang)]
|
||||
|
@ -1,8 +1,13 @@
|
||||
/+ pill
|
||||
=* card card:agent:gall
|
||||
|%
|
||||
+$ any-state $%(state state-0)
|
||||
+$ state
|
||||
+$ state state-1
|
||||
+$ any-state
|
||||
$~ *state
|
||||
$% state-1
|
||||
state-0
|
||||
==
|
||||
+$ state-1
|
||||
$: %1
|
||||
mass-timer=[way=wire nex=@da tim=@dr]
|
||||
==
|
||||
@ -18,7 +23,8 @@
|
||||
^- state
|
||||
[%1 mass-timer:(~(got by hoc.s) 0)]
|
||||
--
|
||||
|= [=bowl:gall sat=state]
|
||||
|= [=bowl:gall sat=any-state]
|
||||
?> ?=(%1 -.sat)
|
||||
=| moz=(list card)
|
||||
|%
|
||||
++ this .
|
||||
|
@ -11,23 +11,33 @@
|
||||
+$ state-1 [%1 pith-1]
|
||||
+$ state-0 [%0 pith-0]
|
||||
+$ any-state
|
||||
$~ *state
|
||||
$% state-3
|
||||
state-2
|
||||
state-1
|
||||
state-0
|
||||
==
|
||||
+$ pith-3
|
||||
+$ pith-3 ::
|
||||
$: rem=(map desk per-desk) ::
|
||||
syn=(map kiln-sync let=@ud) ::
|
||||
ark=(map desk arak) ::
|
||||
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
|
||||
:: map desk to the currently ongoing fuse request
|
||||
:: and the latest version numbers for beaks to
|
||||
fus=(map desk per-fuse)
|
||||
:: used for fuses - every time we get a fuse we
|
||||
:: bump this. used when calculating hashes to
|
||||
:: ensure they're unique even when the same
|
||||
:: request is made multiple times.
|
||||
hxs=(map desk @ud)
|
||||
== ::
|
||||
+$ pith-2 ::
|
||||
$: rem=(map desk per-desk) ::
|
||||
syn=(map kiln-sync let=@ud) ::
|
||||
ota=(unit [=ship =desk =aeon]) ::
|
||||
ark=(map desk *) ::
|
||||
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
|
||||
fus=(map desk per-fuse)
|
||||
hxs=(map desk @ud)
|
||||
== ::
|
||||
+$ pith-1 ::
|
||||
$: rem=(map desk per-desk) ::
|
||||
@ -52,6 +62,15 @@
|
||||
sud=@tas :: from desk
|
||||
cas=case :: at case
|
||||
==
|
||||
+$ per-fuse :: per fuse state
|
||||
:: map [ship desk] to latest version number we
|
||||
:: have for them. used for things we're %trak-ing
|
||||
:: our invariant here is to store the latest version
|
||||
:: number we've heard of.
|
||||
$: mox=(map [ship desk] let=@ud)
|
||||
:: relevant parts of originating request
|
||||
kf=kiln-fuse-data
|
||||
==
|
||||
+$ kiln-commit term ::
|
||||
+$ kiln-mount ::
|
||||
$: pax=path ::
|
||||
@ -76,23 +95,29 @@
|
||||
cas=case ::
|
||||
gim=?(%auto germ) ::
|
||||
==
|
||||
+$ fuse-source [who=ship des=desk ver=$@(%trak case)]
|
||||
:: actual poke
|
||||
+$ kiln-fuse
|
||||
$@ ~
|
||||
$: syd=desk
|
||||
bas=beak
|
||||
con=(list [beak germ])
|
||||
$@ ~ :: signifies clearing the fuse
|
||||
$: overwrite=flag :: force overwrite previous fuse
|
||||
bas=fuse-source
|
||||
con=(list [fuse-source germ])
|
||||
==
|
||||
==
|
||||
:: $diff: subscription update
|
||||
:: state tracked by kiln
|
||||
+$ kiln-fuse-data
|
||||
$: syd=desk
|
||||
bas=fuse-source
|
||||
con=(list [fuse-source germ])
|
||||
==
|
||||
:: Request to list current fuses. ~ means "list all"
|
||||
::
|
||||
+$ diff
|
||||
$% [%block =desk =arak =weft blockers=(set desk)]
|
||||
[%reset =desk =arak]
|
||||
[%merge =desk =arak]
|
||||
[%merge-sunk =desk =arak =tang]
|
||||
[%merge-fail =desk =arak =tang]
|
||||
==
|
||||
+$ kiln-fuse-list (unit desk)
|
||||
--
|
||||
|= [bowl:gall state]
|
||||
|= [bowl:gall any-state]
|
||||
?> ?=(%3 +<+<)
|
||||
?> =(src our)
|
||||
=| moz=(list card:agent:gall)
|
||||
|%
|
||||
@ -124,50 +149,54 @@
|
||||
(turn daz start-dude:~(pass vats [%base *arak]))
|
||||
::
|
||||
++ on-load
|
||||
=> |%
|
||||
+$ ota [syd=desk her=ship sud=desk]
|
||||
--
|
||||
=| old-ota=(unit ota)
|
||||
|= [hood-version=@ud old=any-state]
|
||||
=< abet
|
||||
=? . ?=(%0 -.old)
|
||||
=/ recognized-ota=(unit [syd=desk her=ship sud=desk])
|
||||
=/ syncs=(list [[syd=desk her=ship sud=desk] =aeon])
|
||||
~(tap by syn.old)
|
||||
|- ^- (unit [syd=desk her=ship sud=desk])
|
||||
?~ syncs
|
||||
~
|
||||
?: &(=(%base syd.i.syncs) !=(our her.i.syncs) =(%kids sud.i.syncs))
|
||||
`[syd her sud]:i.syncs
|
||||
$(syncs t.syncs)
|
||||
::
|
||||
=/ sen=(map kiln-sync let=@ud)
|
||||
?~ recognized-ota
|
||||
syn.old
|
||||
(~(del by syn.old) [syd her sud]:u.recognized-ota)
|
||||
:: note that the new state has not yet been initialized
|
||||
::
|
||||
:: TODO reinstate with +vats
|
||||
:: =? ..abet ?=(^ recognized-ota)
|
||||
:: (poke:update `[her sud]:u.recognized-ota)
|
||||
::
|
||||
+>(old [%1 rem.old syn=sen ota=~ commit-timer.old])
|
||||
=? old-ota ?=(%0 -.old)
|
||||
=/ syncs=(list [ota =aeon]) ~(tap by syn.old)
|
||||
|- ^- (unit ota)
|
||||
?~ syncs
|
||||
~
|
||||
?: &(=([%base %kids] [syd sud]:i.syncs) !=(our her.i.syncs))
|
||||
`[syd her sud]:i.syncs
|
||||
$(syncs t.syncs)
|
||||
::
|
||||
=? old ?=(%0 -.old)
|
||||
=? syn.old ?=(^ old-ota) (~(del by syn.old) u.old-ota)
|
||||
[%3 [rem syn ark=~ commit-timer fus=~ hxs=~]:old]
|
||||
::
|
||||
=? old ?=(%1 -.old)
|
||||
:* %2
|
||||
rem.old
|
||||
syn.old
|
||||
ota.old
|
||||
ark=~ :: TODO reinstate old ota here?
|
||||
commit-timer.old
|
||||
fus=~
|
||||
hxs=~
|
||||
==
|
||||
::
|
||||
=? old-ota ?=(%2 -.old)
|
||||
?~ ota.old ~
|
||||
`[%base ship desk]:u.ota.old
|
||||
::
|
||||
=? old ?=(%2 -.old)
|
||||
:* %3
|
||||
rem.old
|
||||
syn.old
|
||||
ark=~
|
||||
commit-timer.old
|
||||
fus.old
|
||||
hxs.old
|
||||
==
|
||||
::
|
||||
?> ?=(%3 -.old)
|
||||
=. +<+.$.abet old
|
||||
..abet
|
||||
=< abet
|
||||
?~ old-ota
|
||||
kiln
|
||||
abet:(install:vats %base [her sud]:u.old-ota)
|
||||
::
|
||||
++ on-peek
|
||||
|= =path
|
||||
@ -505,6 +534,7 @@
|
||||
%kiln-cancel-autocommit =;(f (f !<(_+<.f vase)) poke-cancel-autocommit)
|
||||
%kiln-commit =;(f (f !<(_+<.f vase)) poke-commit)
|
||||
%kiln-fuse =;(f (f !<(_+<.f vase)) poke-fuse)
|
||||
%kiln-fuse-list =;(f (f !<(_+<.f vase)) poke-fuse-list)
|
||||
%kiln-gall-sear =;(f (f !<(_+<.f vase)) poke-gall-sear)
|
||||
%kiln-info =;(f (f !<(_+<.f vase)) poke-info)
|
||||
%kiln-install =;(f (f !<(_+<.f vase)) poke-install)
|
||||
@ -557,10 +587,69 @@
|
||||
[/kiln/autocommit (add now recur) recur mon]
|
||||
(emit %pass way.commit-timer %arvo %b [%wait nex.commit-timer])
|
||||
::
|
||||
++ poke-fuse-list
|
||||
=>
|
||||
|%
|
||||
++ format-fuse
|
||||
|= [into=desk pf=per-fuse]
|
||||
^- tank
|
||||
=/ sources=tape
|
||||
%+ reel
|
||||
con.kf.pf
|
||||
|= [[fs=fuse-source g=germ] acc=tape]
|
||||
^- tape
|
||||
:(weld " [" (format-fuse-source fs) " " <g> "]" acc)
|
||||
:- %leaf
|
||||
;: weld
|
||||
"|fuse {<into>} "
|
||||
(format-fuse-source bas.kf.pf)
|
||||
sources
|
||||
==
|
||||
:: +format-fuse-source: fuse source -> beak -> path
|
||||
::
|
||||
++ format-fuse-source
|
||||
|= fs=fuse-source
|
||||
^- tape
|
||||
=/ bec=beak [who.fs des.fs ?:(?=([%trak] ver.fs) [%tas %track] ver.fs)]
|
||||
<(en-beam [bec /])>
|
||||
--
|
||||
|= k=kiln-fuse-list
|
||||
^+ abet
|
||||
%. abet
|
||||
?~ k
|
||||
?~ fus
|
||||
(slog [leaf+"no ongoing fuses" ~])
|
||||
%- slog
|
||||
%+ roll
|
||||
~(tap by `(map desk per-fuse)`fus)
|
||||
|= [[syd=desk pf=per-fuse] acc=tang]
|
||||
^- tang
|
||||
[(format-fuse syd pf) acc]
|
||||
=/ pfu=(unit per-fuse) (~(get by fus) u.k)
|
||||
?~ pfu
|
||||
(slog [leaf+"no ongoing fuse for {<u.k>}" ~])
|
||||
(slog [(format-fuse u.k u.pfu) ~])
|
||||
::
|
||||
++ poke-fuse
|
||||
|= k=kiln-fuse
|
||||
?~ k abet
|
||||
abet:(emit [%pass /kiln/fuse/[syd.k] %arvo %c [%fuse syd.k bas.k con.k]])
|
||||
=/ payload +.k
|
||||
?~ payload
|
||||
:: cancelling an ongoing fuse
|
||||
%- (slog [leaf+"cancelling fuse into {<syd.k>}" ~])
|
||||
=/ f (fuzz syd.k now)
|
||||
?~ f
|
||||
abet
|
||||
abet:abet:delete:u.f
|
||||
?: &(!overwrite.payload (~(has by fus) syd.k))
|
||||
((slog [leaf+"existing fuse into {<syd.k>} - need =overwrite &" ~]) abet)
|
||||
=. fus (~(put by fus) syd.k [~ [syd.k bas.payload con.payload]])
|
||||
=/ old-cnt=@ud (~(gut by hxs) syd.k 0)
|
||||
=. hxs (~(put by hxs) syd.k +(old-cnt))
|
||||
=/ f (fuzz syd.k now)
|
||||
?~ f
|
||||
abet
|
||||
abet:abet:fuse:u.f
|
||||
::
|
||||
++ poke-gall-sear
|
||||
|= =ship
|
||||
@ -705,6 +794,21 @@
|
||||
[%autocommit *] %+ take-wake-autocommit t.wire
|
||||
?>(?=(%wake +<.sign-arvo) +>.sign-arvo)
|
||||
[%vats *] abet:(take:vats t.wire sign-arvo)
|
||||
[%fuse-request @tas *]
|
||||
=/ f (fuzz i.t.wire now)
|
||||
?~ f
|
||||
abet
|
||||
abet:abet:(take:u.f t.t.wire sign-arvo)
|
||||
[%fuse @tas *] ?> ?=(%mere +<.sign-arvo)
|
||||
=/ syd=desk i.t.wire
|
||||
?. ?=([%| *] +>.sign-arvo)
|
||||
?~ p.p.sign-arvo
|
||||
abet
|
||||
=/ msg=tape "fuse merge conflict for {<syd>}"
|
||||
%- (slog [leaf+msg >p.p.sign-arvo< ~])
|
||||
abet
|
||||
%- (slog leaf+"failed fuse for {<syd>}" p.p.sign-arvo)
|
||||
abet
|
||||
*
|
||||
?+ +<.sign-arvo
|
||||
((slog leaf+"kiln: strange card {<+<.sign-arvo wire>}" ~) abet)
|
||||
@ -788,6 +892,122 @@
|
||||
++ spam
|
||||
|= mes=(list tank)
|
||||
((slog mes) ..spam)
|
||||
:: state machine for fuses
|
||||
::
|
||||
++ fuzz
|
||||
|= [syd=desk now=@da]
|
||||
=/ pfu=(unit per-fuse) (~(get by fus) syd)
|
||||
?~ pfu
|
||||
~
|
||||
=* kf kf.u.pfu
|
||||
=* mox mox.u.pfu
|
||||
=/ should-delete=flag |
|
||||
%- some
|
||||
|%
|
||||
:: finalize
|
||||
::
|
||||
++ abet
|
||||
?: should-delete
|
||||
..fuzz(fus (~(del by fus) syd))
|
||||
..fuzz(fus (~(put by fus) syd [mox kf]))
|
||||
::
|
||||
++ delete
|
||||
^+ ..delete
|
||||
=. should-delete &
|
||||
..delete
|
||||
:: queue moves
|
||||
::
|
||||
++ blab
|
||||
|= new=(list card:agent:gall)
|
||||
^+ +>
|
||||
+>.$(moz (welp new moz))
|
||||
:: +make-requests: send requests for each %trak source.
|
||||
::
|
||||
++ make-requests
|
||||
^+ ..abet
|
||||
=/ movs=(list card:agent:gall)
|
||||
%+ murn
|
||||
[[bas.kf *germ] con.kf]
|
||||
|= [fs=fuse-source germ]
|
||||
^- (unit card:agent:gall)
|
||||
?^ ver.fs
|
||||
:: static source, don't need to track
|
||||
~
|
||||
=/ bec=beak (realize-fuse-source fs &)
|
||||
?> =(who.fs p.bec)
|
||||
?> =(des.fs q.bec)
|
||||
=/ hax=@ud (mug [kf (~(got by hxs) syd)])
|
||||
=/ wir=wire
|
||||
/kiln/fuse-request/[syd]/(scot %p p.bec)/[q.bec]/(scot %ud hax)
|
||||
=/ rav=rave [%sing %w r.bec /]
|
||||
=/ rif=riff [q.bec `rav]
|
||||
`[%pass wir %arvo %c [%warp who.fs rif]]
|
||||
:: No need to keep state if all the sources are static
|
||||
?~ movs
|
||||
delete
|
||||
(blab movs)
|
||||
::
|
||||
++ send-fuse
|
||||
^+ ..abet
|
||||
=/ bas=beak (realize-fuse-source bas.kf |)
|
||||
=/ con=(list [beak germ])
|
||||
%+ turn
|
||||
con.kf
|
||||
|= [fs=fuse-source g=germ]
|
||||
[(realize-fuse-source fs |) g]
|
||||
%- blab
|
||||
[%pass /kiln/fuse/[syd] %arvo %c [%fuse syd bas con]]~
|
||||
::
|
||||
++ fuse
|
||||
^+ ..abet
|
||||
send-fuse:make-requests
|
||||
::
|
||||
++ take
|
||||
|= [wir=wire =sign-arvo]
|
||||
^+ ..fuse
|
||||
?> =((lent wir) 3)
|
||||
=/ who=ship (slav %p (snag 0 wir))
|
||||
=/ src=desk (snag 1 wir)
|
||||
=/ hax=@ud (slav %ud (snag 2 wir))
|
||||
?. =(hax (mug [kf (~(got by hxs) syd)]))
|
||||
:: If the hash in the wire doesn't match the current request
|
||||
:: this is a response for a previous fuse that we can ignore.
|
||||
..take
|
||||
?> ?=([?(%clay %behn) %writ *] sign-arvo)
|
||||
=/ gif +.sign-arvo
|
||||
?~ p.gif
|
||||
%- (slog leaf+"|fuse request failed for {<src>} on <who> - cancelling")
|
||||
delete
|
||||
=/ cas=cass:clay !<(cass:clay +.r.u.p.gif)
|
||||
=. mox (~(put by mox) [who src] ud.cas)
|
||||
fuse
|
||||
::
|
||||
:: utility functions below
|
||||
::
|
||||
:: +realize-fuse-source: convert a fuse-source to a
|
||||
:: fully realized beak.
|
||||
::
|
||||
++ realize-fuse-source
|
||||
|= [fs=fuse-source incr=flag]
|
||||
^- beak
|
||||
:+ who.fs
|
||||
des.fs
|
||||
?@ ver.fs
|
||||
(realize-case [who.fs des.fs incr])
|
||||
`case`ver.fs
|
||||
::
|
||||
++ realize-case
|
||||
|= [who=ship des=desk incr=flag]
|
||||
^- case
|
||||
=/ let=(unit @ud) (~(get by mox) [who des])
|
||||
^- case
|
||||
?~ let
|
||||
da+now
|
||||
:- %ud
|
||||
?: incr
|
||||
+(u.let)
|
||||
u.let
|
||||
--
|
||||
::
|
||||
++ auto
|
||||
|= kiln-sync
|
||||
|
@ -232,58 +232,76 @@
|
||||
:: +veri:dawn: validate keys, life, discontinuity, &c
|
||||
::
|
||||
++ veri
|
||||
|= [=seed:jael =point:azimuth =live]
|
||||
^- (unit error=term)
|
||||
=/ rac (clan:title who.seed)
|
||||
=/ cub (nol:nu:crub:crypto key.seed)
|
||||
?- rac
|
||||
%pawn
|
||||
:: a comet address is the fingerprint of the keypair
|
||||
::
|
||||
?. =(who.seed `@`fig:ex:cub)
|
||||
`%key-mismatch
|
||||
:: a comet can never be breached
|
||||
::
|
||||
?^ live
|
||||
`%already-booted
|
||||
:: a comet can never be re-keyed
|
||||
::
|
||||
?. ?=(%1 lyf.seed)
|
||||
`%invalid-life
|
||||
~
|
||||
|= [=ship =feed:jael =point:azimuth =live]
|
||||
^- (each seed:jael (lest error=term))
|
||||
|^ ?@ -.feed
|
||||
?^ err=(test feed) |+[u.err ~]
|
||||
&+feed
|
||||
?> ?=([%1 ~] -.feed)
|
||||
=| errs=(list term)
|
||||
|-
|
||||
?~ kyz.feed
|
||||
|+?~(errs [%no-key ~] errs)
|
||||
=/ =seed:jael [who [lyf key ~]:i.kyz]:feed
|
||||
?~ err=(test seed)
|
||||
&+seed
|
||||
=. errs (snoc errs u.err)
|
||||
$(kyz.feed t.kyz.feed)
|
||||
::
|
||||
%earl
|
||||
~
|
||||
::
|
||||
*
|
||||
:: on-chain ships must be launched
|
||||
++ test
|
||||
|= =seed:jael
|
||||
^- (unit error=term)
|
||||
?. =(ship who.seed) `%not-our-key
|
||||
=/ rac (clan:title who.seed)
|
||||
=/ cub (nol:nu:crub:crypto key.seed)
|
||||
?- rac
|
||||
%pawn
|
||||
:: a comet address is the fingerprint of the keypair
|
||||
::
|
||||
?. =(who.seed `@`fig:ex:cub)
|
||||
`%key-mismatch
|
||||
:: a comet can never be breached
|
||||
::
|
||||
?^ live
|
||||
`%already-booted
|
||||
:: a comet can never be re-keyed
|
||||
::
|
||||
?. ?=(%1 lyf.seed)
|
||||
`%invalid-life
|
||||
~
|
||||
::
|
||||
?~ net.point
|
||||
`%not-keyed
|
||||
=* net u.net.point
|
||||
:: boot keys must match the contract
|
||||
%earl
|
||||
~
|
||||
::
|
||||
?. =(pub:ex:cub pass.net)
|
||||
~& [%key-mismatch pub:ex:cub pass.net]
|
||||
`%key-mismatch
|
||||
:: life must match the contract
|
||||
::
|
||||
?. =(lyf.seed life.net)
|
||||
`%life-mismatch
|
||||
:: the boot life must be greater than and discontinuous with
|
||||
:: the last seen life (per the sponsor)
|
||||
::
|
||||
?: ?& ?=(^ live)
|
||||
?| ?=(%| breach.u.live)
|
||||
(lte life.net life.u.live)
|
||||
== ==
|
||||
`%already-booted
|
||||
:: produce the sponsor for vere
|
||||
::
|
||||
~? !has.sponsor.net
|
||||
[%no-sponsorship-guarantees-from who.sponsor.net]
|
||||
~
|
||||
==
|
||||
*
|
||||
:: on-chain ships must be launched
|
||||
::
|
||||
?~ net.point
|
||||
`%not-keyed
|
||||
=* net u.net.point
|
||||
:: boot keys must match the contract
|
||||
::
|
||||
?. =(pub:ex:cub pass.net)
|
||||
`%key-mismatch
|
||||
:: life must match the contract
|
||||
::
|
||||
?. =(lyf.seed life.net)
|
||||
`%life-mismatch
|
||||
:: the boot life must be greater than and discontinuous with
|
||||
:: the last seen life (per the sponsor)
|
||||
::
|
||||
?: ?& ?=(^ live)
|
||||
?| ?=(%| breach.u.live)
|
||||
(lte life.net life.u.live)
|
||||
== ==
|
||||
`%already-booted
|
||||
:: produce the sponsor for vere
|
||||
::
|
||||
~? !has.sponsor.net
|
||||
[%no-sponsorship-guarantees-from who.sponsor.net]
|
||||
~
|
||||
==
|
||||
--
|
||||
:: +sponsor:dawn: retreive sponsor from point
|
||||
::
|
||||
++ sponsor
|
||||
|
15
pkg/arvo/mar/metadata/update-2.hoon
Normal file
15
pkg/arvo/mar/metadata/update-2.hoon
Normal file
@ -0,0 +1,15 @@
|
||||
/+ store=metadata-store
|
||||
|_ =update:store
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun update
|
||||
++ json (update:enjs:store update)
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun update:store
|
||||
++ json action:dejs:store
|
||||
--
|
||||
--
|
34
pkg/arvo/mar/notify/client-action.hoon
Normal file
34
pkg/arvo/mar/notify/client-action.hoon
Normal file
@ -0,0 +1,34 @@
|
||||
/- *notify
|
||||
|_ act=client-action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun client-action
|
||||
++ json
|
||||
|= jon=^json
|
||||
=, dejs:format
|
||||
^- client-action
|
||||
|^
|
||||
%. jon
|
||||
%- of
|
||||
:~ connect-provider+connect-provider
|
||||
remove-provider+remove-provider
|
||||
==
|
||||
++ connect-provider
|
||||
%- ot
|
||||
:~ who+(su fed:ag)
|
||||
service+so
|
||||
address+so
|
||||
==
|
||||
++ remove-provider
|
||||
%- ot
|
||||
:~ who+(su fed:ag)
|
||||
service+so
|
||||
==
|
||||
--
|
||||
--
|
||||
--
|
12
pkg/arvo/mar/notify/provider-action.hoon
Normal file
12
pkg/arvo/mar/notify/provider-action.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/- *notify
|
||||
|_ act=provider-action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun provider-action
|
||||
--
|
||||
--
|
12
pkg/arvo/mar/notify/update.hoon
Normal file
12
pkg/arvo/mar/notify/update.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/- *notify
|
||||
|_ upd=update
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun update
|
||||
--
|
||||
--
|
12
pkg/arvo/mar/transform-add-nodes.hoon
Normal file
12
pkg/arvo/mar/transform-add-nodes.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/- *post
|
||||
|_ i=indexed-post
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun i
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun indexed-post
|
||||
--
|
||||
--
|
@ -2,6 +2,15 @@
|
||||
=, clay
|
||||
=* dude dude:gall
|
||||
|%
|
||||
:: $diff: subscription update
|
||||
::
|
||||
+$ diff
|
||||
$% [%block =desk =arak =weft blockers=(set desk)]
|
||||
[%reset =desk =arak]
|
||||
[%merge =desk =arak]
|
||||
[%merge-sunk =desk =arak =tang]
|
||||
[%merge-fail =desk =arak =tang]
|
||||
==
|
||||
:: $arak: foreign vat tracker
|
||||
::
|
||||
:: .next is a list of pending commits with future kelvins
|
||||
|
30
pkg/arvo/sur/notify.hoon
Normal file
30
pkg/arvo/sur/notify.hoon
Normal file
@ -0,0 +1,30 @@
|
||||
/- resource, graph-store
|
||||
|%
|
||||
+$ provider-action
|
||||
$% [%add service=term notify=@t binding=@t auth-token=@t =whitelist]
|
||||
[%remove service=term]
|
||||
[%client-join service=term address=@t]
|
||||
[%client-leave service=term]
|
||||
==
|
||||
::
|
||||
+$ client-action
|
||||
$% [%connect-provider who=@p service=term address=@t]
|
||||
[%remove-provider who=@p service=term]
|
||||
==
|
||||
::
|
||||
+$ notification
|
||||
$: =resource:resource
|
||||
=index:graph-store
|
||||
==
|
||||
::
|
||||
+$ whitelist
|
||||
$: public=?
|
||||
kids=?
|
||||
users=(set ship)
|
||||
groups=(set resource:resource)
|
||||
==
|
||||
::
|
||||
+$ update
|
||||
$% [%notification =notification]
|
||||
==
|
||||
--
|
@ -1883,7 +1883,12 @@
|
||||
[%public-keys =public-keys-result] :: ethereum changes
|
||||
[%turf turf=(list turf)] :: domains
|
||||
== ::
|
||||
:: +seed: private boot parameters
|
||||
:: +feed: potential boot parameters
|
||||
::
|
||||
+$ feed
|
||||
$^ [[%1 ~] who=ship kyz=(list [lyf=life key=ring])]
|
||||
seed
|
||||
:: +seed: individual boot parameters
|
||||
::
|
||||
+$ seed [who=ship lyf=life key=ring sig=(unit oath:pki)]
|
||||
::
|
||||
|
@ -2054,7 +2054,7 @@
|
||||
|= [k=beak v=(unit dome:clay)]
|
||||
^- tank
|
||||
=/ received=tape ?~(v "missing" "received")
|
||||
leaf+"{<k>} {received}"
|
||||
leaf+"{<(en-beam k ~)>} {received}"
|
||||
:_ discarded
|
||||
leaf+"fusing into {<syd>} from {<bas>} {<con>} - overwriting prior fuse"
|
||||
=. fiz (make-melt bas con)
|
||||
@ -2071,8 +2071,11 @@
|
||||
:: responses we get for the merge will cause take-fuse to crash
|
||||
::
|
||||
=. fiz *melt
|
||||
((slog [leaf+"clay: fuse failed, missing {<bec>}"]~) ..take-fuse)
|
||||
?> (~(has by sto.fiz) bec)
|
||||
=/ msg=tape <(en-beam bec ~)>
|
||||
((slog [leaf+"clay: fuse failed, missing {msg}"]~) ..take-fuse)
|
||||
?. (~(has by sto.fiz) bec)
|
||||
=/ msg=tape <(en-beam bec ~)>
|
||||
((slog [leaf+"clay: got strange fuse response {<msg>}"]~) ..take-fuse)
|
||||
=. fiz
|
||||
:+ bas.fiz con.fiz
|
||||
(~(put by sto.fiz) bec `!<(dome:clay q.r.u.riot))
|
||||
@ -2093,7 +2096,6 @@
|
||||
|-
|
||||
^+ ..take-fuse
|
||||
?~ merges
|
||||
=/ t=tang [leaf+"{<syd>} fused from {<bas.fiz>} {<con.fiz>}" ~]
|
||||
=. ..take-fuse (done-fuse clean-state %& ~)
|
||||
(park | [%| continuation-yaki(p (flop parents))] rag)
|
||||
=/ [bec=beak g=germ] i.merges
|
||||
@ -2101,7 +2103,8 @@
|
||||
=/ result (merge-helper p.bec q.bec g ali-dom `continuation-yaki)
|
||||
?- -.result
|
||||
%|
|
||||
(done-fuse clean-state %| %fuse-merge-failed p.result)
|
||||
=/ failing-merge=tape "{<bec>} {<g>}"
|
||||
(done-fuse clean-state %| %fuse-merge-failed leaf+failing-merge p.result)
|
||||
::
|
||||
%&
|
||||
=/ merge-result=(unit merge-result) +.result
|
||||
|
@ -277,14 +277,19 @@
|
||||
::
|
||||
[%gall %unto *]
|
||||
:: ~& [%take-gall-unto +>.sih]
|
||||
?- -.+>.sih
|
||||
%raw-fact !!
|
||||
%poke-ack ?~(p.p.+>.sih +>.$ (crud %coup u.p.p.+>.sih))
|
||||
%kick drum-watch
|
||||
%watch-ack ?~ p.p.+>.sih
|
||||
+>.$
|
||||
(dump:(crud %reap u.p.p.+>.sih) %logo ~)
|
||||
%fact (from ;;(dill-blit q:`vase`+>+>.sih))
|
||||
?- -.+>.sih
|
||||
%raw-fact !!
|
||||
%kick drum-watch
|
||||
%poke-ack ?~(p.p.+>.sih +>.$ (crud %coup u.p.p.+>.sih))
|
||||
%watch-ack
|
||||
?~ p.p.+>.sih
|
||||
+>.$
|
||||
(dump:(crud %reap u.p.p.+>.sih) %logo ~)
|
||||
::
|
||||
%fact
|
||||
?. ?=(%dill-blit p.cage.p.+>.sih)
|
||||
+>.$
|
||||
(from ;;(dill-blit q.q.cage.p.+>.sih))
|
||||
==
|
||||
::
|
||||
[%clay %note *]
|
||||
|
@ -1210,15 +1210,21 @@
|
||||
?~ maybe-channel=(~(get by session.channel-state.state) channel-id)
|
||||
%^ return-static-data-on-duct 404 'text/html'
|
||||
(error-page 404 %.y url.request ~)
|
||||
:: if there's already a duct listening to this channel, we must 400
|
||||
::
|
||||
?: ?=([%| *] state.u.maybe-channel)
|
||||
%^ return-static-data-on-duct 400 'text/html'
|
||||
(error-page 400 %.y url.request "channel already bound")
|
||||
:: when opening an event-stream, we must cancel our timeout timer
|
||||
:: if there's no duct already bound. Else, kill the old request
|
||||
:: and replace it
|
||||
::
|
||||
=. moves
|
||||
[(cancel-timeout-move channel-id p.state.u.maybe-channel) moves]
|
||||
=^ cancel-moves state
|
||||
?. ?=([%| *] state.u.maybe-channel)
|
||||
:_ state
|
||||
(cancel-timeout-move channel-id p.state.u.maybe-channel)^~
|
||||
=/ cancel-heartbeat
|
||||
?~ heartbeat.u.maybe-channel ~
|
||||
:_ ~
|
||||
%+ cancel-heartbeat-move channel-id
|
||||
[date duct]:u.heartbeat.u.maybe-channel
|
||||
=- [(weld cancel-heartbeat -<) ->]
|
||||
(handle-response(duct p.state.u.maybe-channel) [%cancel ~])
|
||||
:: the request may include a 'Last-Event-Id' header
|
||||
::
|
||||
=/ maybe-last-event-id=(unit @ud)
|
||||
@ -1289,7 +1295,7 @@
|
||||
|= =channel
|
||||
channel(events ~, state [%| duct], heartbeat (some [heartbeat-time duct]))
|
||||
::
|
||||
[[heartbeat (weld http-moves moves)] state]
|
||||
[[heartbeat :(weld http-moves cancel-moves moves)] state]
|
||||
:: +acknowledge-events: removes events before :last-event-id on :channel-id
|
||||
::
|
||||
++ acknowledge-events
|
||||
@ -1735,7 +1741,7 @@
|
||||
=/ res
|
||||
%- handle-response
|
||||
:* %continue
|
||||
data=(some (as-octs:mimes:html '\0a'))
|
||||
data=(some (as-octs:mimes:html ':\0a'))
|
||||
complete=%.n
|
||||
==
|
||||
=/ http-moves -.res
|
||||
|
@ -771,12 +771,10 @@
|
||||
mo-core
|
||||
=^ [=duct =routes blocker=(each deal unto)] blocked
|
||||
~(get to blocked)
|
||||
?: ?=(%| -.blocker) $
|
||||
=/ =move
|
||||
=/ =sock [attributing.routes our]
|
||||
=/ card
|
||||
?: ?=(%& -.blocker)
|
||||
[%slip %g %deal sock dap p.blocker]
|
||||
[%pass /clear-huck %b %huck `sign-arvo`[%gall %unto p.blocker]]
|
||||
=/ card [%slip %g %deal sock dap p.blocker]
|
||||
[duct card]
|
||||
$(moves [move moves])
|
||||
:: +mo-filter-queue: remove all blocked tasks from ship.
|
||||
|
@ -434,7 +434,7 @@
|
||||
%- curd =< abet
|
||||
(private-keys:~(feel su hen now pki etn) life.tac ring.tac)
|
||||
::
|
||||
:: update private keys
|
||||
:: register moon keys
|
||||
::
|
||||
%moon
|
||||
?. =(%earl (clan:title ship.tac))
|
||||
@ -718,6 +718,14 @@
|
||||
=/ a-point=point (~(gut by pos.zim.pki) ship.i.udiffs *point)
|
||||
=/ a-diff=(unit diff:point) (udiff-to-diff:point udiff.i.udiffs a-point)
|
||||
=? this-su ?=(^ a-diff)
|
||||
:: if this about our keys, and we already know these, start using them
|
||||
::
|
||||
=? lyf.own
|
||||
?& =(our ship.i.udiffs)
|
||||
?=(%keys -.u.a-diff)
|
||||
(~(has by jaw.own) life.to.u.a-diff)
|
||||
==
|
||||
life.to.u.a-diff
|
||||
(public-keys:feel original-pos %diff ship.i.udiffs u.a-diff)
|
||||
$(udiffs t.udiffs)
|
||||
::
|
||||
@ -927,7 +935,16 @@
|
||||
^+ ..feel
|
||||
?: &(=(lyf.own life) =((~(get by jaw.own) life) `ring))
|
||||
..feel
|
||||
=. lyf.own life
|
||||
:: only eagerly update lyf if we were behind the chain life
|
||||
::
|
||||
=? lyf.own
|
||||
?& (gth life lyf.own)
|
||||
::
|
||||
=+ pon=(~(get by pos.zim) our)
|
||||
?~ pon |
|
||||
(lth lyf.own life.u.pon)
|
||||
==
|
||||
life
|
||||
=. jaw.own (~(put by jaw.own) life ring)
|
||||
(exec yen.own [%give %private-keys lyf.own jaw.own])
|
||||
::
|
||||
|
@ -11,7 +11,7 @@
|
||||
|= arg=vase
|
||||
=/ m (strand ,vase)
|
||||
^- form:m
|
||||
=+ !<(desks=(list desk) arg)
|
||||
=+ !<([~ desks=(list desk)] arg)
|
||||
=? desks =(~ desks) [%work]~
|
||||
|- ^- form:m
|
||||
=* loop $
|
||||
|
@ -188,37 +188,53 @@
|
||||
++ test-veri-good
|
||||
=/ sed [~zod 1 sec ~]
|
||||
%+ expect-eq
|
||||
!> ~
|
||||
!> (veri:dawn sed pot ~)
|
||||
!> &+sed
|
||||
!> (veri:dawn ~zod sed pot ~)
|
||||
::
|
||||
++ test-veri-not-spawned
|
||||
=/ sed [~zod 1 sec ~]
|
||||
%+ expect-eq
|
||||
!> `%not-keyed
|
||||
!> (veri:dawn sed =>(pot .(net ~)) ~)
|
||||
!> |+[%not-keyed ~]
|
||||
!> (veri:dawn ~zod sed =>(pot .(net ~)) ~)
|
||||
::
|
||||
++ test-veri-wrong-key
|
||||
=/ sed [~zod 1 sec:ex:(pit:nu:crub:crypto 24 %foo) ~]
|
||||
%+ expect-eq
|
||||
!> `%key-mismatch
|
||||
!> (veri:dawn sed pot ~)
|
||||
!> |+[%key-mismatch ~]
|
||||
!> (veri:dawn ~zod sed pot ~)
|
||||
::
|
||||
++ test-veri-life-mismatch
|
||||
=/ sed [~zod 2 sec ~]
|
||||
%+ expect-eq
|
||||
!> `%life-mismatch
|
||||
!> (veri:dawn sed pot ~)
|
||||
!> |+[%life-mismatch ~]
|
||||
!> (veri:dawn ~zod sed pot ~)
|
||||
::
|
||||
++ test-veri-bad-multikey
|
||||
=/ fed=feed:jael
|
||||
:- [%1 ~]
|
||||
:- ~zod
|
||||
:~ [1 sec:ex:(pit:nu:crub:crypto 24 %foo)]
|
||||
[2 sec]
|
||||
==
|
||||
%+ expect-eq
|
||||
!> |+[%key-mismatch %life-mismatch ~]
|
||||
!> (veri:dawn ~zod fed pot ~)
|
||||
::
|
||||
++ test-veri-none-multikey
|
||||
%+ expect-eq
|
||||
!> |+[%no-key ~]
|
||||
!> (veri:dawn ~zod [[%1 ~] ~zod ~] pot ~)
|
||||
::
|
||||
++ test-veri-already-booted
|
||||
=/ sed [~zod 1 sec ~]
|
||||
;: weld
|
||||
%+ expect-eq
|
||||
!> `%already-booted
|
||||
!> (veri:dawn sed pot `[1 |])
|
||||
!> |+[%already-booted ~]
|
||||
!> (veri:dawn ~zod sed pot `[1 |])
|
||||
::
|
||||
%+ expect-eq
|
||||
!> `%already-booted
|
||||
!> (veri:dawn sed pot `[2 &])
|
||||
!> |+[%already-booted ~]
|
||||
!> (veri:dawn ~zod sed pot `[2 &])
|
||||
==
|
||||
::
|
||||
++ test-veri-earl-good
|
||||
@ -230,8 +246,8 @@
|
||||
(shaf %earl (sham who 1 pub:ex:cub))
|
||||
[who 1 sec:ex:cub `sig]
|
||||
%+ expect-eq
|
||||
!> ~
|
||||
!> (veri:dawn sed pot ~)
|
||||
!> &+sed
|
||||
!> (veri:dawn who sed pot ~)
|
||||
::
|
||||
++ test-veri-earl-parent-not-keyed
|
||||
=/ cub (pit:nu:crub:crypto 24 %foo)
|
||||
@ -242,38 +258,38 @@
|
||||
(shaf %earl (sham who 1 pub:ex:cub))
|
||||
[who 1 sec:ex:cub `sig]
|
||||
%+ expect-eq
|
||||
!> ~
|
||||
!> (veri:dawn sed =>(pot .(net ~)) ~)
|
||||
!> &+sed
|
||||
!> (veri:dawn who sed =>(pot .(net ~)) ~)
|
||||
::
|
||||
++ test-veri-pawn-good
|
||||
=/ cub (pit:nu:crub:crypto 24 %foo)
|
||||
=/ who=ship `@`fig:ex:cub
|
||||
=/ sed [who 1 sec:ex:cub ~]
|
||||
%+ expect-eq
|
||||
!> ~
|
||||
!> (veri:dawn sed *point:azimuth-types ~)
|
||||
!> &+sed
|
||||
!> (veri:dawn who sed *point:azimuth-types ~)
|
||||
::
|
||||
++ test-veri-pawn-key-mismatch
|
||||
=/ cub (pit:nu:crub:crypto 24 %foo)
|
||||
=/ who=ship `@`fig:ex:cub
|
||||
=/ sed [who 1 sec:ex:(pit:nu:crub:crypto 24 %bar) ~]
|
||||
%+ expect-eq
|
||||
!> `%key-mismatch
|
||||
!> (veri:dawn sed *point:azimuth-types ~)
|
||||
!> |+[%key-mismatch ~]
|
||||
!> (veri:dawn who sed *point:azimuth-types ~)
|
||||
::
|
||||
++ test-veri-pawn-invalid-life
|
||||
=/ cub (pit:nu:crub:crypto 24 %foo)
|
||||
=/ who=ship `@`fig:ex:cub
|
||||
=/ sed [who 2 sec:ex:cub ~]
|
||||
%+ expect-eq
|
||||
!> `%invalid-life
|
||||
!> (veri:dawn sed *point:azimuth-types ~)
|
||||
!> |+[%invalid-life ~]
|
||||
!> (veri:dawn who sed *point:azimuth-types ~)
|
||||
::
|
||||
++ test-veri-pawn-already-booted
|
||||
=/ cub (pit:nu:crub:crypto 24 %foo)
|
||||
=/ who=ship `@`fig:ex:cub
|
||||
=/ sed [who 1 sec:ex:cub ~]
|
||||
%+ expect-eq
|
||||
!> `%already-booted
|
||||
!> (veri:dawn sed *point:azimuth-types `[1 |])
|
||||
!> |+[%already-booted ~]
|
||||
!> (veri:dawn who sed *point:azimuth-types `[1 |])
|
||||
--
|
||||
|
27
pkg/btc-wallet/.eslintrc.json
Normal file
27
pkg/btc-wallet/.eslintrc.json
Normal 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
1
pkg/btc-wallet/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.eslintcache
|
1
pkg/btc-wallet/.husky/.gitignore
vendored
Normal file
1
pkg/btc-wallet/.husky/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
_
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd pkg/interface && npx lint-staged
|
||||
cd pkg/btc-wallet && npx lint-staged src
|
4
pkg/btc-wallet/.prettierrc
Normal file
4
pkg/btc-wallet/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
@ -5,4 +5,4 @@ dojo:
|
||||
|
||||
it should return with the following hash:
|
||||
|
||||
`0v1.9p61c.bd4vn.deevh.0ldbq.fkqo3`
|
||||
`0v7.v4dng.o33qi.kc497.5jc02.ke5es`
|
||||
|
1849
pkg/btc-wallet/package-lock.json
generated
1849
pkg/btc-wallet/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "urbit-bitcoin-wallet",
|
||||
"name": "btc-wallet",
|
||||
"version": "0.1.0",
|
||||
"main": "node install.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --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",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"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",
|
||||
"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",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
@ -71,5 +78,9 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"natives": "1.1.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --cache --fix",
|
||||
"*.{js,css,md}": "prettier --write"
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import './css/custom.css';
|
||||
|
||||
// rebuild x3
|
||||
|
||||
window.NETWORK = 'testnet'; // 'bitcoin'
|
||||
|
||||
const channel = new window.channel();
|
||||
api.setChannel(window.ship, channel);
|
||||
|
||||
|
@ -7,10 +7,13 @@ class UrbitApi {
|
||||
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]);
|
||||
|
||||
window.subscriptionId = this.channel.subscribe(ship, appl, path,
|
||||
window.subscriptionId = this.channel.subscribe(
|
||||
ship,
|
||||
appl,
|
||||
path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
@ -19,32 +22,38 @@ class UrbitApi {
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
path,
|
||||
},
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
fail(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
btcWalletCommand(data) {
|
||||
return this.action("btc-wallet", "btc-wallet-command", data);
|
||||
return this.action('btc-wallet', 'btc-wallet-command', data);
|
||||
}
|
||||
|
||||
settingsEvent(data) {
|
||||
return this.action("settings-store", "settings-event", data);
|
||||
return this.action('settings-store', 'settings-event', data);
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.channel.poke(ship, appl, mark, data,
|
||||
this.channel.poke(
|
||||
this.ship,
|
||||
appl,
|
||||
mark,
|
||||
data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import Send from './send.js'
|
||||
import CurrencyPicker from './currencyPicker.js'
|
||||
import { currencyToSats, satsToCurrency } from '../../lib/util.js';
|
||||
|
||||
import { Row, Text, Button, Col } from '@tlon/indigo-react';
|
||||
import Send from './send.js';
|
||||
import CurrencyPicker from './currencyPicker.js';
|
||||
import { satsToCurrency } from '../../lib/util.js';
|
||||
import { store } from '../../store.js';
|
||||
|
||||
export default class Balance extends Component {
|
||||
constructor(props) {
|
||||
@ -21,7 +13,7 @@ export default class Balance extends Component {
|
||||
sending: false,
|
||||
copiedButton: false,
|
||||
copiedString: false,
|
||||
}
|
||||
};
|
||||
|
||||
this.copyAddress = this.copyAddress.bind(this);
|
||||
}
|
||||
@ -29,110 +21,149 @@ export default class Balance extends Component {
|
||||
copyAddress(arg) {
|
||||
let address = this.props.state.address;
|
||||
navigator.clipboard.writeText(address);
|
||||
this.props.api.btcWalletCommand({'gen-new-address': null});
|
||||
this.props.api.btcWalletCommand({ 'gen-new-address': null });
|
||||
|
||||
if (arg === 'button'){
|
||||
this.setState({copiedButton: true});
|
||||
setTimeout(() => { this.setState({copiedButton: false}); }, 2000);
|
||||
if (arg === 'button') {
|
||||
this.setState({ copiedButton: true });
|
||||
setTimeout(() => {
|
||||
this.setState({ copiedButton: false });
|
||||
}, 2000);
|
||||
} else if (arg === 'string') {
|
||||
this.setState({copiedString: true});
|
||||
setTimeout(() => { this.setState({copiedString: false}); }, 2000);
|
||||
this.setState({ copiedString: true });
|
||||
setTimeout(() => {
|
||||
this.setState({ copiedString: false });
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const sats = (this.props.state.confirmedBalance || 0);
|
||||
const sats = this.props.state.confirmedBalance || 0;
|
||||
const unconfirmedSats = this.props.state.unconfirmedBalance;
|
||||
|
||||
const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : '';
|
||||
|
||||
const denomination = this.props.state.denomination;
|
||||
const value = satsToCurrency(sats, denomination, 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);
|
||||
const value = satsToCurrency(
|
||||
sats,
|
||||
denomination,
|
||||
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);
|
||||
|
||||
const conversion = this.props.state.currencyRates[denomination].last;
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.state.sending ?
|
||||
<Send
|
||||
state={this.props.state}
|
||||
api={api}
|
||||
psbt={this.props.state.psbt}
|
||||
fee={this.props.state.fee}
|
||||
currencyRates={this.props.state.currencyRates}
|
||||
shipWallets={this.props.state.shipWallets}
|
||||
value={value}
|
||||
denomination={denomination}
|
||||
sats={sats}
|
||||
conversion={conversion}
|
||||
network={this.props.network}
|
||||
error={this.props.state.error}
|
||||
stopSending={() => {
|
||||
this.setState({sending: false});
|
||||
store.handleEvent({data: {psbt: '', fee: 0, error: '', "broadcast-fail": null}});
|
||||
}}
|
||||
/> :
|
||||
<Col
|
||||
height="400px"
|
||||
width='100%'
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
justifyContent="space-between"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row justifyContent="space-between">
|
||||
<Text color="orange" fontSize={1}>Balance</Text>
|
||||
<Text color="lightGray" fontSize="14px" mono style={{cursor: "pointer"}}
|
||||
onClick={() => {this.copyAddress('string')}}>
|
||||
{this.state.copiedString ? "copied" : addressText}
|
||||
</Text>
|
||||
<CurrencyPicker
|
||||
api={this.props.api}
|
||||
denomination={denomination}
|
||||
currencies={this.props.state.currencyRates}
|
||||
/>
|
||||
</Row>
|
||||
<Col justifyContent="center" alignItems="center">
|
||||
<Text fontSize="40px" color="orange" style={{whiteSpace: "nowrap"}} >
|
||||
{value}
|
||||
</Text>
|
||||
<Text fontSize={1} color="orange">{`${sats}${unconfirmedString} sats`}</Text>
|
||||
</Col>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Button children="Send"
|
||||
disabled={sendDisabled}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={sendDisabled ? "lighterGray" : "white"}
|
||||
backgroundColor={sendDisabled ? "veryLightGray" : "orange"}
|
||||
style={{cursor: sendDisabled ? "default" : "pointer" }}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => this.setState({sending: true})}
|
||||
/>
|
||||
<Button children={(this.state.copiedButton) ? "Address Copied!" : "Copy Address"}
|
||||
mr={3}
|
||||
disabled={this.state.copiedButton}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={(this.state.copiedButton) ? "green" : "orange"}
|
||||
backgroundColor={(this.state.copiedButton) ? "veryLightGreen" : "midOrange" }
|
||||
style={{cursor: (this.state.copiedButton) ? "default" : "pointer"}}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => {this.copyAddress('button')}}
|
||||
/>
|
||||
</Row>
|
||||
{this.state.sending ? (
|
||||
<Send
|
||||
state={this.props.state}
|
||||
api={this.props.api}
|
||||
psbt={this.props.state.psbt}
|
||||
fee={this.props.state.fee}
|
||||
currencyRates={this.props.state.currencyRates}
|
||||
shipWallets={this.props.state.shipWallets}
|
||||
value={value}
|
||||
denomination={denomination}
|
||||
sats={sats}
|
||||
conversion={conversion}
|
||||
network={this.props.network}
|
||||
error={this.props.state.error}
|
||||
stopSending={() => {
|
||||
this.setState({ sending: false });
|
||||
store.handleEvent({
|
||||
data: { psbt: '', fee: 0, error: '', 'broadcast-fail': null },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Col
|
||||
height="400px"
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
justifyContent="space-between"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row justifyContent="space-between">
|
||||
<Text color="orange" fontSize={1}>
|
||||
Balance
|
||||
</Text>
|
||||
<Text
|
||||
color="lightGray"
|
||||
fontSize="14px"
|
||||
mono
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
this.copyAddress('string');
|
||||
}}
|
||||
>
|
||||
{this.state.copiedString ? 'copied' : addressText}
|
||||
</Text>
|
||||
<CurrencyPicker
|
||||
api={this.props.api}
|
||||
denomination={denomination}
|
||||
currencies={this.props.state.currencyRates}
|
||||
/>
|
||||
</Row>
|
||||
<Col justifyContent="center" alignItems="center">
|
||||
<Text
|
||||
fontSize="40px"
|
||||
color="orange"
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={1}
|
||||
color="orange"
|
||||
>{`${sats}${unconfirmedString} sats`}</Text>
|
||||
</Col>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Button
|
||||
disabled={sendDisabled}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={sendDisabled ? 'lighterGray' : 'white'}
|
||||
backgroundColor={sendDisabled ? 'veryLightGray' : 'orange'}
|
||||
style={{ cursor: sendDisabled ? 'default' : 'pointer' }}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => this.setState({ sending: true })}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
mr={3}
|
||||
disabled={this.state.copiedButton}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={this.state.copiedButton ? 'green' : 'orange'}
|
||||
backgroundColor={
|
||||
this.state.copiedButton ? 'veryLightGreen' : 'midOrange'
|
||||
}
|
||||
style={{
|
||||
cursor: this.state.copiedButton ? 'default' : 'pointer',
|
||||
}}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => {
|
||||
this.copyAddress('button');
|
||||
}}
|
||||
>
|
||||
{this.state.copiedButton ? 'Address Copied!' : 'Copy Address'}
|
||||
</Button>
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import {
|
||||
StatelessTextInput,
|
||||
Icon,
|
||||
Row,
|
||||
Input,
|
||||
LoadingSpinner,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
@ -23,9 +22,9 @@ export default class ProviderModal extends Component {
|
||||
ready: false,
|
||||
provider: null,
|
||||
connecting: false,
|
||||
}
|
||||
};
|
||||
|
||||
this.checkProvider = this.checkProvider.bind(this);
|
||||
this.checkProvider = this.checkProvider.bind(this);
|
||||
this.submitProvider = this.submitProvider.bind(this);
|
||||
}
|
||||
|
||||
@ -38,77 +37,88 @@ export default class ProviderModal extends Component {
|
||||
|
||||
if (isValidPatp(provider)) {
|
||||
let command = {
|
||||
"check-provider": provider
|
||||
}
|
||||
'check-provider': provider,
|
||||
};
|
||||
potentialProvider = provider;
|
||||
checkingProvider = true;
|
||||
this.props.api.btcWalletCommand(command);
|
||||
setTimeout(() => {
|
||||
this.setState({providerFailed: true, checkingProvider: false});
|
||||
this.setState({ providerFailed: true, checkingProvider: false });
|
||||
}, 5000);
|
||||
}
|
||||
this.setState({provider, ready, checkingProvider, potentialProvider});
|
||||
this.setState({ provider, ready, checkingProvider, potentialProvider });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState){
|
||||
if (!this.state.ready){
|
||||
componentDidUpdate() {
|
||||
if (!this.state.ready) {
|
||||
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){
|
||||
if (this.state.ready){
|
||||
submitProvider() {
|
||||
if (this.state.ready) {
|
||||
let command = {
|
||||
"set-provider": this.state.provider
|
||||
}
|
||||
'set-provider': this.state.provider,
|
||||
};
|
||||
this.props.api.btcWalletCommand(command);
|
||||
this.setState({connecting: true});
|
||||
this.setState({ connecting: true });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let workingNode = null;
|
||||
let workingColor = null;
|
||||
let workingBg = null;
|
||||
if (this.state.ready) {
|
||||
workingColor = "green";
|
||||
workingBg = "veryLightGreen"
|
||||
workingNode =
|
||||
workingColor = 'green';
|
||||
workingBg = 'veryLightGreen';
|
||||
workingNode = (
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" color="green">
|
||||
{this.state.provider} is a working provider node
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
} else if (this.state.providerFailed) {
|
||||
workingColor = "red";
|
||||
workingBg = "veryLightRed"
|
||||
workingNode =
|
||||
workingColor = 'red';
|
||||
workingBg = 'veryLightRed';
|
||||
workingNode = (
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" color="red">
|
||||
{this.state.potentialProvider} is not a working provider node
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
padding={3}
|
||||
>
|
||||
<Box width="100%" height="100%" padding={3}>
|
||||
<Row>
|
||||
<Icon icon="Bitcoin" mr={2}/>
|
||||
<Icon icon="Bitcoin" mr={2} />
|
||||
<Text fontSize="14px" fontWeight="bold">
|
||||
Step 1 of 2: Set up Bitcoin Provider Node
|
||||
</Text>
|
||||
</Row>
|
||||
<Box mt={3}>
|
||||
<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.
|
||||
<a fontSize="14px" target="_blank" href="https://urbit.org/bitcoin-wallet"> Learn More</a>
|
||||
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.
|
||||
<a
|
||||
fontSize="14px"
|
||||
target="_blank"
|
||||
href="https://urbit.org/bitcoin-wallet"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{' '}
|
||||
Learn More
|
||||
</a>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={3} mb={2}>
|
||||
@ -132,7 +142,7 @@ export default class ProviderModal extends Component {
|
||||
borderColor={workingColor}
|
||||
onChange={this.checkProvider}
|
||||
/>
|
||||
{(this.state.checkingProvider) ? <LoadingSpinner/> : null}
|
||||
{this.state.checkingProvider ? <LoadingSpinner /> : null}
|
||||
</Row>
|
||||
{workingNode}
|
||||
<Row alignItems="center" mt={3}>
|
||||
@ -140,12 +150,15 @@ export default class ProviderModal extends Component {
|
||||
mr={2}
|
||||
primary
|
||||
disabled={!this.state.ready}
|
||||
children="Set Peer Node"
|
||||
fontSize="14px"
|
||||
style={{cursor: this.state.ready ? "pointer" : "default"}}
|
||||
onClick={() => {this.submitProvider(this.state.provider)}}
|
||||
/>
|
||||
{(this.state.connecting) ? <LoadingSpinner/> : null}
|
||||
style={{ cursor: this.state.ready ? 'pointer' : 'default' }}
|
||||
onClick={() => {
|
||||
this.submitProvider(this.state.provider);
|
||||
}}
|
||||
>
|
||||
Set Peer Node
|
||||
</Button>
|
||||
{this.state.connecting ? <LoadingSpinner /> : null}
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,70 +1,71 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
LoadingSpinner,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { Sigil } from './sigil.js'
|
||||
|
||||
import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react';
|
||||
|
||||
export default class TxAction extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const leftIcon =
|
||||
(this.props.action === "sent") ? "ArrowSouth" :
|
||||
(this.props.action === "recv") ? "ArrowNorth" :
|
||||
(this.props.action === "fail") ? "X" :
|
||||
"NullIcon";
|
||||
this.props.action === 'sent'
|
||||
? 'ArrowSouth'
|
||||
: this.props.action === 'recv'
|
||||
? 'ArrowNorth'
|
||||
: this.props.action === 'fail'
|
||||
? 'X'
|
||||
: 'NullIcon';
|
||||
|
||||
const actionColor =
|
||||
(this.props.action === "sent") ? "sentBlue" :
|
||||
(this.props.action === "recv") ? "recvGreen" :
|
||||
(this.props.action === "fail") ? "gray" :
|
||||
"red";
|
||||
this.props.action === 'sent'
|
||||
? 'sentBlue'
|
||||
: this.props.action === 'recv'
|
||||
? 'recvGreen'
|
||||
: this.props.action === 'fail'
|
||||
? 'gray'
|
||||
: 'red';
|
||||
|
||||
const actionText =
|
||||
(this.props.action === "sent" && !this.props.pending) ? "Sent BTC" :
|
||||
(this.props.action === "sent" && this.props.pending) ? "Sending BTC" :
|
||||
(this.props.action === "recv" && !this.props.pending) ? "Received BTC" :
|
||||
(this.props.action === "recv" && this.props.pending) ? "Receiving BTC" :
|
||||
(this.props.action === "fail") ? "Failed" :
|
||||
"error";
|
||||
this.props.action === 'sent' && !this.props.pending
|
||||
? 'Sent BTC'
|
||||
: this.props.action === 'sent' && this.props.pending
|
||||
? 'Sending BTC'
|
||||
: this.props.action === 'recv' && !this.props.pending
|
||||
? 'Received BTC'
|
||||
: this.props.action === 'recv' && this.props.pending
|
||||
? 'Receiving BTC'
|
||||
: this.props.action === 'fail'
|
||||
? 'Failed'
|
||||
: 'error';
|
||||
|
||||
const pending = (!this.props.pending) ? null :
|
||||
<LoadingSpinner
|
||||
background="midOrange"
|
||||
foreground="orange"
|
||||
/>
|
||||
const pending = !this.props.pending ? null : (
|
||||
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||
);
|
||||
|
||||
const url = (this.props.network === 'testnet')
|
||||
? `http://blockstream.info/testnet/tx/${this.props.txid}`
|
||||
: `http://blockstream.info/tx/${this.props.txid}`;
|
||||
const url =
|
||||
this.props.network === 'testnet'
|
||||
? `http://blockstream.info/testnet/tx/${this.props.txid}`
|
||||
: `http://blockstream.info/tx/${this.props.txid}`;
|
||||
|
||||
return (
|
||||
<Row alignItems="center">
|
||||
<Box backgroundColor={actionColor}
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
mr={2}
|
||||
p={1}
|
||||
<Box
|
||||
backgroundColor={actionColor}
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
mr={2}
|
||||
p={1}
|
||||
>
|
||||
<Icon icon={leftIcon} color="white"/>
|
||||
<Icon icon={leftIcon} color="white" />
|
||||
</Box>
|
||||
<Text color={actionColor} fontSize="14px">{actionText}</Text>
|
||||
<a href={url} target="_blank">
|
||||
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2}/>
|
||||
<Text color={actionColor} fontSize="14px">
|
||||
{actionText}
|
||||
</Text>
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2} />
|
||||
</a>
|
||||
{pending}
|
||||
</Row>
|
||||
|
@ -1,28 +1,16 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route } from "react-router-dom";
|
||||
import _ from 'lodash';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { api } from '../api.js';
|
||||
import { store } from '../store.js';
|
||||
|
||||
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import light from './themes/light';
|
||||
import dark from './themes/dark';
|
||||
import {
|
||||
Text,
|
||||
Box,
|
||||
Reset,
|
||||
Col,
|
||||
LoadingSpinner,
|
||||
} from '@tlon/indigo-react';
|
||||
// import dark from './themes/dark';
|
||||
import { Box, Reset } from '@tlon/indigo-react';
|
||||
import StartupModal from './lib/startupModal.js';
|
||||
import Header from './lib/header.js'
|
||||
import Balance from './lib/balance.js'
|
||||
import Transactions from './lib/transactions.js'
|
||||
import Warning from './lib/warning.js'
|
||||
import Body from './lib/body.js'
|
||||
import { subscription } from '../subscription.js'
|
||||
import Body from './lib/body.js';
|
||||
import { subscription } from '../subscription.js';
|
||||
|
||||
const network = "bitcoin";
|
||||
const network = 'bitcoin';
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
@ -32,8 +20,8 @@ export class Root extends Component {
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.props.channel.setOnChannelError((e) => {
|
||||
componentDidMount() {
|
||||
this.props.channel.setOnChannelError(() => {
|
||||
subscription.start();
|
||||
});
|
||||
subscription.start();
|
||||
@ -42,34 +30,38 @@ export class Root extends Component {
|
||||
render() {
|
||||
const loaded = this.state.loaded;
|
||||
const warning = this.state.showWarning;
|
||||
const blur = (!loaded) ? false :
|
||||
!(this.state.wallet && this.state.provider);
|
||||
const blur = !loaded ? false : !(this.state.wallet && this.state.provider);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ThemeProvider theme={light}>
|
||||
<Reset/>
|
||||
{ (loaded) ? <StartupModal api={api} state={this.state} network={network}/> : null }
|
||||
<Box display="flex"
|
||||
flexDirection='column'
|
||||
position='absolute'
|
||||
alignItems='center'
|
||||
backgroundColor='lightOrange'
|
||||
width='100%'
|
||||
<Reset />
|
||||
{loaded ? (
|
||||
<StartupModal api={api} state={this.state} network={network} />
|
||||
) : null}
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
position="absolute"
|
||||
alignItems="center"
|
||||
backgroundColor="lightOrange"
|
||||
width="100%"
|
||||
minHeight={loaded ? '100%' : 'none'}
|
||||
height={loaded ? 'none' : '100%'}
|
||||
style={{filter: (blur ? 'blur(8px)' : 'none')}}
|
||||
px={[0,4]}
|
||||
pb={[0,4]}
|
||||
style={{ filter: blur ? 'blur(8px)' : 'none' }}
|
||||
px={[0, 4]}
|
||||
pb={[0, 4]}
|
||||
>
|
||||
<Body loaded={loaded}
|
||||
<Body
|
||||
loaded={loaded}
|
||||
state={this.state}
|
||||
api={api} network={network}
|
||||
api={api}
|
||||
network={network}
|
||||
warning={warning}
|
||||
/>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,11 @@ module.exports = {
|
||||
extends: '@urbit',
|
||||
env: {
|
||||
'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', {}]
|
||||
}
|
||||
};
|
||||
|
@ -36,8 +36,34 @@ export const globalTypes = {
|
||||
|
||||
export const decorators = [
|
||||
(Story, context) => {
|
||||
window.ship = 'sampel-palnet';
|
||||
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({
|
||||
associations: {
|
||||
groups: {
|
||||
@ -66,6 +92,25 @@ export const decorators = [
|
||||
},
|
||||
},
|
||||
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': {
|
||||
metadata: {
|
||||
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({
|
||||
|
@ -28,12 +28,13 @@ module.exports = {
|
||||
```
|
||||
|
||||
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
|
||||
'pkg/interface':
|
||||
same (if [developing on a local development ship][local]). Then, from the root
|
||||
of the repository
|
||||
|
||||
```
|
||||
npm ci
|
||||
npm run start
|
||||
```bash
|
||||
npm i
|
||||
npm run bootstrap
|
||||
cd pkg/interface && npm run start
|
||||
```
|
||||
|
||||
The dev server will start at `http://localhost:9000`. Sign in as you would
|
||||
|
51815
pkg/interface/package-lock.json
generated
51815
pkg/interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@reach/disclosure": "^0.10.5",
|
||||
@ -13,7 +14,8 @@
|
||||
"@tlon/indigo-light": "^1.0.7",
|
||||
"@tlon/indigo-react": "^1.2.23",
|
||||
"@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",
|
||||
"aws-sdk": "^2.830.0",
|
||||
"big-integer": "^1.6.48",
|
||||
@ -80,7 +82,7 @@
|
||||
"@types/yup": "^0.29.11",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.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",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
@ -102,6 +104,7 @@
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"sass": "^1.32.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"storybook-addon-designs": "^6.0.0",
|
||||
"ts-mdast": "^1.0.0",
|
||||
"typescript": "^4.2.4",
|
||||
"webpack": "^4.46.0",
|
||||
@ -113,15 +116,15 @@
|
||||
"lint-file": "eslint",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
"preinstall": "./preinstall.sh",
|
||||
"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",
|
||||
"test": "jest",
|
||||
"prepare": "cd ../.. && husky install pkg/interface/.husky",
|
||||
"test": "tsc && jest",
|
||||
"jest": "jest",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"chromatic": "chromatic --exit-zero-on-changes"
|
||||
"chromatic": "chromatic --exit-zero-on-changes",
|
||||
"hook-lint": "eslint --cache --fix"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
|
@ -9,4 +9,6 @@ for i in $(find . -type d -maxdepth 1) ; do
|
||||
npm ci
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
done
|
||||
cd http-api
|
||||
npm run build
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
47
pkg/interface/src/logic/api/bootstrap.ts
Normal file
47
pkg/interface/src/logic/api/bootstrap.ts
Normal 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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
8
pkg/interface/src/logic/api/index.ts
Normal file
8
pkg/interface/src/logic/api/index.ts
Normal 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;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 } });
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
} } });
|
||||
}
|
||||
}
|
25
pkg/interface/src/logic/lib/contact.ts
Normal file
25
pkg/interface/src/logic/lib/contact.ts
Normal 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;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
16
pkg/interface/src/logic/lib/fakeApi.test.js
Normal file
16
pkg/interface/src/logic/lib/fakeApi.test.js
Normal 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);
|
||||
});
|
||||
});
|
22
pkg/interface/src/logic/lib/fakeApi.ts
Normal file
22
pkg/interface/src/logic/lib/fakeApi.ts
Normal 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;
|
||||
}
|
45
pkg/interface/src/logic/lib/fixtures.ts
Normal file
45
pkg/interface/src/logic/lib/fixtures.ts
Normal 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
|
||||
}
|
||||
];
|
||||
};
|
@ -12,14 +12,10 @@
|
||||
// intrinsic expiry.
|
||||
//
|
||||
//
|
||||
import GlobalApi from '../api/global';
|
||||
import useStorageState from '../state/storage';
|
||||
|
||||
class GcpManager {
|
||||
#api: GlobalApi | null = null;
|
||||
|
||||
configure(api: GlobalApi) {
|
||||
this.#api = api;
|
||||
configure() {
|
||||
}
|
||||
|
||||
#running = false;
|
||||
@ -30,10 +26,6 @@ class GcpManager {
|
||||
console.warn('GcpManager already running');
|
||||
return;
|
||||
}
|
||||
if (!this.#api) {
|
||||
console.error('GcpManager must have api set');
|
||||
return;
|
||||
}
|
||||
this.#running = true;
|
||||
this.refreshLoop();
|
||||
}
|
||||
@ -63,7 +55,7 @@ class GcpManager {
|
||||
|
||||
private refreshLoop() {
|
||||
if (!this.#configured) {
|
||||
this.#api!.gcp.isConfigured()
|
||||
useStorageState.getState().gcp.isConfigured()
|
||||
.then((configured) => {
|
||||
if (configured === undefined) {
|
||||
throw new Error('can\'t check whether GCP is configured?');
|
||||
@ -82,7 +74,7 @@ class GcpManager {
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.#api!.gcp.getToken()
|
||||
useStorageState.getState().gcp.getToken()
|
||||
.then(() => {
|
||||
const token = useStorageState.getState().gcp.token;
|
||||
if (token) {
|
||||
|
27
pkg/interface/src/logic/lib/graph.ts
Normal file
27
pkg/interface/src/logic/lib/graph.ts
Normal 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('/')}`;
|
||||
}
|
@ -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');
|
||||
};
|
||||
}
|
@ -10,8 +10,18 @@ const makeIndexes = () => new Map([
|
||||
['other', []]
|
||||
]);
|
||||
|
||||
export interface OmniboxItem {
|
||||
title: string;
|
||||
link: string;
|
||||
app: string;
|
||||
host: string;
|
||||
description: string;
|
||||
shiftLink: string;
|
||||
shiftDescription: string;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
'title': title,
|
||||
'link': link,
|
||||
@ -93,7 +103,7 @@ const otherIndex = function(config) {
|
||||
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();
|
||||
indexes.set('ships', shipIndex(contacts));
|
||||
// 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'];
|
||||
if (each['app-name'] === 'contacts') {
|
||||
app = 'groups';
|
||||
};
|
||||
}
|
||||
|
||||
if (each['app-name'] === 'graph') {
|
||||
app = each.metadata.config.graph;
|
||||
@ -159,4 +169,4 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
||||
indexes.set('other', otherIndex(hide));
|
||||
|
||||
return indexes;
|
||||
};
|
||||
}
|
38
pkg/interface/src/logic/lib/suspend.ts
Normal file
38
pkg/interface/src/logic/lib/suspend.ts
Normal 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
|
||||
};
|
||||
}
|
@ -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 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}`;
|
||||
|
||||
@ -33,9 +33,10 @@ const raceRegexes = (str) => {
|
||||
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];
|
||||
const perma = parsePermalink(convertToGroupRef(groupRef[2]));
|
||||
content = permalinkToReference(perma);
|
||||
sfix = groupRef[3];
|
||||
}
|
||||
|
@ -113,4 +113,19 @@ describe('tokenizeMessage', () => {
|
||||
expect(text).toBe('. foo');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
const files: File[] = [];
|
||||
@ -43,7 +43,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
|
||||
}
|
||||
setDragging(true);
|
||||
},
|
||||
[setDragging]
|
||||
[]
|
||||
);
|
||||
|
||||
const onDrop = useCallback(
|
||||
@ -56,7 +56,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
|
||||
e.preventDefault();
|
||||
dragged(files, e);
|
||||
},
|
||||
[setDragging, dragged]
|
||||
[dragged]
|
||||
);
|
||||
|
||||
const onDragOver = useCallback(
|
||||
@ -77,7 +77,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
|
||||
setDragging(false);
|
||||
}
|
||||
},
|
||||
[setDragging]
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -92,12 +92,12 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
|
||||
};
|
||||
}, []);
|
||||
|
||||
const bind = {
|
||||
const bind = useMemo(() => ({
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
onDragEnter
|
||||
};
|
||||
}), [onDragEnter, onDragOver, onDrop, onDragEnter]);
|
||||
|
||||
return { bind, dragging };
|
||||
return useMemo(() => ({ bind, dragging }), [bind, dragging]);
|
||||
}
|
||||
|
77
pkg/interface/src/logic/lib/useFileUpload.ts
Normal file
77
pkg/interface/src/logic/lib/useFileUpload.ts
Normal 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
|
||||
};
|
||||
}
|
@ -36,10 +36,10 @@ export function useLazyScroll(
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if((oldCount > count) && ref.current) {
|
||||
if((oldCount > count) && ref.current && !isLoading) {
|
||||
loadUntil(ref.current);
|
||||
}
|
||||
}, [count]);
|
||||
}, [count, isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!ready) {
|
||||
@ -48,7 +48,7 @@ export function useLazyScroll(
|
||||
}, [ready]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current || isDone || !ready) {
|
||||
if (!ref.current || isDone || !ready || isLoading) {
|
||||
return;
|
||||
}
|
||||
const scroll = ref.current;
|
||||
@ -64,7 +64,7 @@ export function useLazyScroll(
|
||||
return () => {
|
||||
ref.current?.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
}, [ref?.current, ready, isDone]);
|
||||
}, [ref?.current, ready, isDone, isLoading]);
|
||||
|
||||
return { isDone, isLoading };
|
||||
}
|
||||
|
@ -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);
|
||||
if (s) {
|
||||
try {
|
||||
@ -12,26 +12,16 @@ function retrieve<T>(key: string, initial: T): T {
|
||||
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 {
|
||||
const [state, _setState] = useState(() => retrieve(key, initial));
|
||||
const [state, setState] = useState(() => retrieve(key, initial));
|
||||
|
||||
useEffect(() => {
|
||||
_setState(retrieve(key, initial));
|
||||
setState(retrieve(key, initial));
|
||||
}, [key]);
|
||||
|
||||
const setState = useCallback(
|
||||
(s: SetState<T>) => {
|
||||
const updated = typeof s === 'function' ? s(state) : s;
|
||||
_setState(updated);
|
||||
localStorage.setItem(key, JSON.stringify(updated));
|
||||
},
|
||||
[_setState, key, state]
|
||||
);
|
||||
useEffect(() => {
|
||||
localStorage.setItem(key, JSON.stringify(state));
|
||||
}, [state]);
|
||||
|
||||
return [state, setState] as const;
|
||||
return useMemo(() => [state, setState] as const, [state, setState]);
|
||||
}
|
||||
|
29
pkg/interface/src/logic/lib/useResize.ts
Normal file
29
pkg/interface/src/logic/lib/useResize.ts
Normal 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;
|
||||
}
|
34
pkg/interface/src/logic/lib/useUrlField.tsx
Normal file
34
pkg/interface/src/logic/lib/useUrlField.tsx
Normal 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;
|
||||
}
|
@ -241,11 +241,13 @@ export function uxToHex(ux: string) {
|
||||
|
||||
export const hexToUx = (hex) => {
|
||||
const ux = f.flow(
|
||||
f.reverse,
|
||||
f.chunk(4),
|
||||
// eslint-disable-next-line prefer-arrow-callback
|
||||
f.map(x => _.dropWhile(x, function(y: unknown) {
|
||||
return y === 0;
|
||||
}).join('')),
|
||||
return y === '0';
|
||||
}).reverse().join('')),
|
||||
f.reverse,
|
||||
f.join('.')
|
||||
)(hex.split(''));
|
||||
return `0x${ux}`;
|
||||
@ -523,3 +525,32 @@ export const favicon = () => {
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { ContactUpdate, deSig } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import { reduceState } from '../state/base';
|
||||
import useContactState, { ContactState } from '../state/contact';
|
||||
import { BaseState } from '../state/base';
|
||||
import { ContactState as State } from '../state/contact';
|
||||
|
||||
type ContactState = State & BaseState<State>;
|
||||
|
||||
const initial = (json: ContactUpdate, state: ContactState): ContactState => {
|
||||
const data = _.get(json, 'initial', false);
|
||||
@ -71,23 +73,18 @@ const setPublic = (json: ContactUpdate, state: ContactState): ContactState => {
|
||||
return state;
|
||||
};
|
||||
|
||||
export const ContactReducer = (json) => {
|
||||
const data: ContactUpdate = _.get(json, 'contact-update', false);
|
||||
if (data) {
|
||||
reduceState<ContactState, ContactUpdate>(useContactState, data, [
|
||||
initial,
|
||||
add,
|
||||
remove,
|
||||
edit,
|
||||
setPublic
|
||||
]);
|
||||
}
|
||||
|
||||
// TODO: better isolation
|
||||
const res = _.get(json, 'resource', false);
|
||||
if (res) {
|
||||
useContactState.setState({
|
||||
nackedContacts: useContactState.getState().nackedContacts.add(`~${res.ship}`)
|
||||
});
|
||||
export const reduceNacks = (json, state: ContactState): ContactState => {
|
||||
const data = json?.resource;
|
||||
if(data) {
|
||||
state.nackedContacts.add(`~${data.res}`);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const reduce = [
|
||||
initial,
|
||||
add,
|
||||
remove,
|
||||
edit,
|
||||
setPublic
|
||||
];
|
||||
|
@ -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');
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user