Merge branch 'develop' into i/6103/ames-refactor

This commit is contained in:
yosoyubik 2023-03-12 09:46:44 +01:00
commit 2724523a26
92 changed files with 146 additions and 32873 deletions

View File

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

View File

@ -1,62 +0,0 @@
name: publish-npm-packages
on:
push:
branches:
- 'master'
paths:
- 'pkg/npm/**'
jobs:
publish-api:
runs-on: ubuntu-latest
name: "Publish '@urbit/api' if a new version is available"
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: 'npm install'
working-directory: 'pkg/npm/api'
- uses: JS-DevTools/npm-publish@v1
with:
check-version: true
package: './pkg/npm/api/package.json'
token: ${{ secrets.NPM_TOKEN }}
publish-http-api:
runs-on: ubuntu-latest
name: "Publish '@urbit/http-api' if a new version is available"
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: 'npm install'
working-directory: 'pkg/npm/http-api'
- uses: JS-DevTools/npm-publish@v1
with:
check-version: true
package: './pkg/npm/http-api/package.json'
token: ${{ secrets.NPM_TOKEN }}
publish-eslint-config:
runs-on: ubuntu-latest
name: "Publish '@urbit/eslint-config' if a new version is available"
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: 'npm install'
working-directory: 'pkg/npm/eslint-config'
- uses: JS-DevTools/npm-publish@v1
with:
check-version: true
package: './pkg/npm/eslint-config/package.json'
token: ${{ secrets.NPM_TOKEN }}

View File

@ -61,12 +61,12 @@ jobs:
# for the docker build. We don't want in on Mac, where it isn't but
# it breaks the nix install. The two `if` clauses should be mutually
# exclusive
- uses: cachix/install-nix-action@v16
- uses: cachix/install-nix-action@v20
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
if: ${{ matrix.type == 'linux' }}
- uses: cachix/install-nix-action@v16
- uses: cachix/install-nix-action@v20
if: ${{ matrix.os != 'ubuntu-latest' }}
- uses: cachix/cachix-action@v10

1
.nvmrc
View File

@ -1 +0,0 @@
16.14.0

View File

@ -1,16 +0,0 @@
bin
doc
extras
nix
pkg/arvo
pkg/base-dev
pkg/docker-image
pkg/ent
pkg/garden
pkg/garden-dev
pkg/ge-additions
pkg/herb
pkg/hs
pkg/libaes_siv
pkg/urbit
sh

View File

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

15716
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
{
"name": "root",
"private": true,
"engines": {
"node": "16.14.0"
},
"devDependencies": {
"eslint": "^7.29.0",
"husky": "^6.0.0",
"lerna": "^4.0.0",
"lint-staged": "^11.1.2",
"prettier": "^2.3.2"
},
"scripts": {
"watch-libs": "lerna run watch --no-private --parallel",
"build-libs": "lerna run build --no-private",
"test": "lerna run test",
"bootstrap": "lerna bootstrap",
"build:prod": "lerna run build:prod"
}
}

View File

@ -2,8 +2,8 @@
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|%
+$ state
$~ [%25 *state:drum *state:helm *state:kiln]
$>(%25 any-state)
$~ [%26 *state:drum *state:helm *state:kiln]
$>(%26 any-state)
::
+$ any-state
$% [ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
@ -26,6 +26,7 @@
[%23 drum=state-4:drum helm=state-2:helm kiln=state-9:kiln]
[%24 drum=state-4:drum helm=state-2:helm kiln=state-10:kiln]
[%25 drum=state-5:drum helm=state-2:helm kiln=state-10:kiln]
[%26 drum=state-6:drum helm=state-2:helm kiln=state-10:kiln]
==
+$ any-state-tuple
$: drum=any-state:drum
@ -124,6 +125,7 @@
|= [=wire syn=sign-arvo]
^- step:agent:gall
?+ wire ~|([%hood-bad-wire wire] !!)
[%drum *] =^(c drum.state (take-arvo:drum-core t.wire syn) [c this])
[%helm *] =^(c helm.state (take-arvo:helm-core t.wire syn) [c this])
[%kiln *] =^(c kiln.state (take-arvo:kiln-core t.wire syn) [c this])
==

View File

@ -1,4 +1,4 @@
:: Helm: Adjust vane error verbosity knob
:: Drum: Adjust vane error verbosity knob
::
/? 310
::
@ -6,5 +6,5 @@
::
:- %say
|= [^ [error-tag=@tas level=?(%hush %soft %loud) ~] ~]
:- %helm-knob
:- %drum-knob
[error-tag level]

View File

@ -1,21 +1,28 @@
/- *sole
/+ sole
|%
+$ state state-5
+$ state state-6
+$ any-state
$~ *state
$% state-5
$% state-6
state-5
state-4
state-3
state-2
==
+$ state-6 [%6 pith-6]
+$ state-5 [%5 pith-5]
+$ state-4 [%4 pith-4]
+$ state-3 [%3 pith-3]
+$ state-2 [%2 pith-2]
::
+$ pith-5
+$ pith-6
$: bin=(map @ source) :: terminals
nob=(map @tas ?(%hush %soft %loud))
==
::
+$ pith-5
$: bin=(map @ source)
==
::
+$ pith-4
@ -130,7 +137,9 @@
++ klr klr:format
+$ state ^state :: proxy
+$ any-state ^any-state :: proxy
++ on-init (poke-link %$ our.hid %dojo)
++ on-init
=+ out=(poke-link %$ our.hid %dojo)
out(- [hear-logs -.out])
::
++ prep
|= s=@tas
@ -191,11 +200,16 @@
=^ txt +> ?@(arg [arg +>] [+.arg (prep -.arg)])
se-abet:(se-blit-sys [%sav pax txt]) ::
::
++ poke-knob
|= [tag=@tas lev=?(%hush %soft %loud)]
se-abet(nob (~(put by nob) tag lev))
::
++ poke
|= [=mark =vase]
?> =(our src):hid
?+ mark ~|([%poke-drum-bad-mark mark] !!)
%dill-poke =;(f (f !<(_+<.f vase)) poke-dill)
%drum-knob =;(f (f !<(_+<.f vase)) poke-knob)
%drum-exit =;(f (f !<(_+<.f vase)) poke-exit)
%drum-link =;(f (f !<(_+<.f vase)) poke-link)
%drum-put =;(f (f !<(_+<.f vase)) poke-put)
@ -229,7 +243,9 @@
[%mod -.b p.b]
--
::
?> ?=(%5 -.old)
=? moz ?=(%5 -.old) [hear-logs moz]
=? old ?=(%5 -.old) [%6 bin.old ~]
?> ?=(%6 -.old)
=. sat old
=. dev (~(gut by bin) ses *source)
this
@ -276,6 +292,15 @@
=^ gyl this (open way)
~& [%drum-quit src.hid gyl]
(se-drop %| gyl)
::
++ hear-logs
`card:agent:gall`[%pass /drum/dill/logs %arvo %d %logs [~ ~]]
::
++ take-arvo
|= [way=wire syn=sign-arvo]
?> =(/dill/logs way)
?> ?=(%logs +<.syn)
se-abet:(se-told:(prep %$) told.syn)
:: :: ::
:::: :: ::
:: :: ::
@ -363,6 +388,19 @@
~| [inx=inx wag=wag fug=fug eel=eel]
`(snag inx `(list gill:gall)`wag)
::
++ se-told :: render system output
|= =told:dill
^+ +>
?- -.told
%crud =/ lev (~(gut by nob) p.told %loud)
=? +>.$ !=(%hush lev)
(se-text "crud: %{(trip p.told)} event failed")
?. =(%loud lev) +>.$
(se-dump q.told)
%talk (se-dump (flop p.told)) ::NOTE +se-dump flops internally
%text (se-text p.told)
==
::
++ se-belt :: handle input
|= bet=dill-belt:dill
^+ +>
@ -438,7 +476,7 @@
(se-blit %mor [%hop 0] [%wyp ~] lin [%nel ~] ~)
::
++ se-dump :: print tanks
|= tac=(list tank)
|= tac=tang
^+ +>
=/ wol=wall
(zing (turn (flop tac) |=(a=tank (~(win re a) [0 edg]))))

View File

@ -253,10 +253,6 @@
|= cong=[msg=@ud mem=@ud] =< abet
(emit %pass /helm %arvo %a %cong cong)
::
++ poke-knob
|= [error-tag=@tas level=?(%hush %soft %loud)] =< abet
(emit %pass /helm %arvo %d %knob error-tag level)
::
++ poke-serve
|= [=binding:eyre =generator:eyre] =< abet
(emit %pass /helm/serv %arvo %e %serve binding generator)
@ -300,7 +296,6 @@
%helm-gall-sift =;(f (f !<(_+<.f vase)) poke-gall-sift)
%helm-gall-verb =;(f (f !<(_+<.f vase)) poke-gall-verb)
%helm-hi =;(f (f !<(_+<.f vase)) poke-hi)
%helm-knob =;(f (f !<(_+<.f vase)) poke-knob)
%helm-pans =;(f (f !<(_+<.f vase)) poke-pans)
%helm-mass =;(f (f !<(_+<.f vase)) poke-mass)
%helm-meld =;(f (f !<(_+<.f vase)) poke-meld)

View File

@ -0,0 +1,10 @@
/- eval=ted-eval
|_ put=inpt:eval
++ grab |%
++ noun inpt:eval
--
++ grow |%
++ noun put
--
++ grad %noun
--

View File

@ -0,0 +1,5 @@
^?
|%
+$ deps (list path)
+$ inpt $@(cord (pair cord deps))
--

View File

@ -50,12 +50,7 @@
|: [a=`@`1 b=`@`1]
:: quotient
^- @
~_ leaf+"divide-by-zero"
?< =(0 b)
=+ c=0
|-
?: (lth a b) c
$(a (sub a b), c +(c))
-:(dvr a b)
::
++ dvr
~/ %dvr
@ -63,11 +58,16 @@
::
:: a: dividend
:: b: divisor
|= [a=@ b=@]
|: [a=`@`1 b=`@`1]
:: p: quotient
:: q: remainder
^- [p=@ q=@]
[(div a b) (mod a b)]
~_ leaf+"divide-by-zero"
?< =(0 b)
=+ c=0
|-
?: (lth a b) [c a]
$(a (sub a b), c +(c))
::
++ gte
~/ %gte
@ -150,8 +150,7 @@
|: [a=`@`1 b=`@`1]
:: the remainder
^- @
?< =(0 b)
(sub a (mul b (div a b)))
+:(dvr a b)
::
++ mul
~/ %mul

View File

@ -1215,26 +1215,26 @@
[%meld ~] :: unify memory
[%pack ~] :: compact memory
[%trim p=@ud] :: trim kernel state
[%logs =told] :: system output
== ::
+$ task :: in request ->$
$~ [%vega ~] ::
$% [%boot lit=? p=*] :: weird %dill boot
[%crop p=@ud] :: trim kernel state
[%crud p=@tas q=(list tank)] :: print error
[%flog p=flog] :: wrapped error
[%heft ~] :: memory report
$>(%init vane-task) :: after gall ready
[%logs p=(unit ~)] :: watch system output
[%meld ~] :: unify memory
[%pack ~] :: compact memory
[%seat =desk] :: install desk
[%shot ses=@tas task=session-task] :: task for session
[%talk p=(list tank)] :: print tanks
[%text p=tape] :: print tape
$>(%trim vane-task) :: trim state
$>(%vega vane-task) :: report upgrade
[%verb ~] :: verbose mode
[%knob tag=term level=?(%hush %soft %loud)] :: error verbosity
[%knob tag=term level=?(%hush %soft %loud)] :: deprecated removeme
session-task :: for default session
told :: system output
== ::
:: ::
+$ session-task :: session request
@ -1246,6 +1246,12 @@
[%shut ~] :: close session
[%view ~] :: watch session blits
== ::
:: ::
+$ told :: system output
$% [%crud p=@tas q=tang] :: error
[%talk p=(list tank)] :: tanks (in order)
[%text p=tape] :: tape
== ::
::
:::: :: (1d2)
::
@ -1279,7 +1285,7 @@
== ::
+$ dill-belt :: arvo input
$% belt :: client input
[%cru p=@tas q=(list tank)] :: echo error
[%cru p=@tas q=(list tank)] :: errmsg (deprecated)
[%hey ~] :: refresh
[%rez p=@ud q=@ud] :: resize, cols, rows
[%yow p=gill:gall] :: connect to app
@ -1290,11 +1296,11 @@
== ::
+$ flog :: sent to %dill
$% [%crop p=@ud] :: trim kernel state
[%crud p=@tas q=(list tank)] ::
$>(%crud told) ::
[%heft ~] ::
[%meld ~] :: unify memory
[%pack ~] :: compact memory
[%text p=tape] ::
$>(%text told) ::
[%verb ~] :: verbose mode
== ::
:: ::

View File

@ -8,14 +8,12 @@
-- ::
=> |% :: console protocol
+$ axle ::
$: %6 ::
$: %7 ::
hey=(unit duct) :: default duct
dug=(map @tas axon) :: conversations
eye=(jug @tas duct) :: outside listeners
eye=(jug @tas duct) :: outside observers
ear=(set duct) :: syslog listeners
lit=? :: boot in lite mode
$= veb :: vane verbosities
$~ (~(put by *(map @tas log-level)) %hole %soft) :: quiet packet crashes
(map @tas log-level) ::
egg=_| :: see +take, removeme
== ::
+$ axon :: dill session
@ -97,10 +95,6 @@
?+ -.kyz ~& [%strange-kiss -.kyz] +>
%hail (send %hey ~)
%belt (send `dill-belt`p.kyz)
%talk (talk p.kyz)
%text (fore (tuba p.kyz) ~)
%crud :: (send `dill-belt`[%cru p.kyz q.kyz])
(crud p.kyz q.kyz)
%blew (send(wid p.p.kyz) %rez p.p.kyz q.p.kyz)
%heft (pass /whey %$ whey/~)
%meld (dump kyz)
@ -115,25 +109,11 @@
==
::
++ crud
|= [err=@tas tac=(list tank)]
:: unknown errors default to %loud
::
=/ lev=log-level (~(gut by veb.all) err %loud)
:: apply log level for this error tag
::
?- lev
%hush +>.$
%soft (fore (tuba "crud: %{(trip err)} event failed") ~)
%loud (talk leaf+"crud: %{(trip err)} event failed" (flop tac))
==
::
++ talk
|= tac=(list tank)
%- fore
%- zing
%+ turn tac
|= a=tank
(turn (~(win re a) [0 wid]) tuba)
|= [err=@tas tac=tang]
=- +>.$(moz (weld - moz))
%+ turn
~(tap in ear.all)
(late %give %logs %crud err tac)
::
++ dump :: pass down to hey
|= git=gift
@ -155,30 +135,6 @@
|= [=wire =note]
+>(moz :_(moz [hen %pass wire note]))
::
++ fore :: send dill output
::NOTE there are still implicit assumptions
:: about the underlying console app's
:: semantics here. specifically, trailing
:: newlines are important to not getting
:: overwritten by the drum prompt, and a
:: bottom-of-screen cursor position gives
:: nicest results. a more agnostic solution
:: will need to replace this arm, someday.
:: perhaps +send this to .ram instead?
::
|= liz=(list (list @c))
~? !=(%$ ses) [%d %foreing-in-session ses]
^+ +>
=. +>
=| biz=(list blit)
|- ^+ +>.^$
?~ liz (done %blit [%hop 0] [%wyp ~] biz)
$(liz t.liz, biz (welp biz [%put i.liz] [%nel ~] ~))
:: since dill is acting on its own accord,
:: we %hey the term app so it may clean up.
::
(send %hey ~)
::
++ from :: receive blit
|= bit=dill-blit
^+ +>
@ -349,10 +305,13 @@
::
?: ?=(?(%trim %vega) -.task)
[~ ..^$]
:: %knob sets a verbosity level for an error tag
:: %knob used to set a verbosity level for an error tag,
:: but dill no longer prints errors itself, so implementing %knob
:: has become a recommendation to error printers (like drum).
:: remove this when %knob gets removed from lull, next™ kelvin release.
::
?: ?=(%knob -.task)
=. veb.all (~(put by veb.all) tag.task level.task)
~& [%dill %knob-deprecated]
[~ ..^$]
:: %open opens a new dill session
::
@ -400,6 +359,18 @@
?: ?=(%flee -.task)
:- ~
..^$(eye.all (~(del ju eye.all) ses hen))
:: %logs opens or closes a subscription to system output
::
?: ?=(%logs -.task)
=. ear.all
?~ p.task (~(del in ear.all) hen)
(~(put in ear.all) hen)
[~ ..^$]
:: if we were $told something, give %logs to all interested parties
::
?: ?=(?(%crud %talk %text) -.task)
:_ ..^$
(turn ~(tap in ear.all) (late %give %logs task))
::
=/ nus
(ax hen ses)
@ -408,8 +379,7 @@
:: could be before %boot (or %boot failed)
::
~& [%dill-call-no-session ses hen -.task]
=/ tan ?:(?=(%crud -.task) q.task ~)
[((slog (flop tan)) ~) ..^$]
[~ ..^$]
::
=^ moz all abet:(call:u.nus task)
[moz ..^$]
@ -417,12 +387,28 @@
++ load :: import old state
=< |= old=any-axle
?- -.old
%6 ..^$(all old)
%7 ..^$(all old)
%6 $(old (axle-6-to-7 old))
%5 $(old (axle-5-to-6 old))
%4 $(old (axle-4-to-5 old))
==
|%
+$ any-axle $%(axle axle-5 axle-4)
+$ any-axle $%(axle axle-6 axle-5 axle-4)
::
+$ axle-6
$: %6
hey=(unit duct)
dug=(map @tas axon)
eye=(jug @tas duct)
lit=?
veb=(map @tas log-level)
egg=_|
==
::
++ axle-6-to-7
|= a=axle-6
^- axle
[%7 hey dug eye ~ lit egg]:a
::
+$ axle-5
$: %5
@ -435,7 +421,7 @@
::
++ axle-5-to-6
|= a=axle-5
^- axle
^- axle-6
:: [%6 hey `(map @tas axon)`dug eye lit veb |]
a(- %6, veb [veb.a &])
::

View File

@ -1,16 +1,11 @@
/- spider
/- spider, eval=ted-eval
/+ strandio
=, strand=strand:spider
=>
|%
+$ deps (list path)
+$ inpt $@(cord (pair cord deps))
--
^- thread:spider
|= raw=vase
=/ m (strand ,vase)
^- form:m
=+ !<(arg=(unit inpt) raw)
=+ !<(arg=(unit inpt:eval) raw)
?~ arg
(strand-fail:strand %no-input ~)
?@ u.arg

View File

@ -1,16 +1,11 @@
/- spider
/- spider, eval=ted-eval
/+ strandio
=, strand=strand:spider
=>
|%
+$ deps (list path)
+$ inpt $@(cord (pair cord deps))
--
^- thread:spider
|= raw=vase
=/ m (strand ,vase)
^- form:m
=+ !<(arg=(unit inpt) raw)
=+ !<(arg=(unit inpt:eval) raw)
?~ arg
(strand-fail:strand %no-input ~)
=/ com

View File

@ -733,7 +733,7 @@
::
%eth-get-block-by-number
:- 'eth_getBlockByNumber'
:~ (tape (num-to-hex bon.req))
:~ (tape (num-to-hex-minimal bon.req))
b+txs.req
==
::
@ -942,6 +942,12 @@
%- render-hex-bytes
(as-octs:mimes:html n)
::
++ num-to-hex-minimal
|= n=@
^- tape
%- prefix-hex
((x-co:co 1) n)
::
++ address-to-hex
|= a=address
^- tape

View File

@ -1 +0,0 @@
Each one of the folders in this directory is published at `@urbit/{folder name}`

View File

@ -1,12 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 1%",
"useBuiltIns": "usage",
"corejs": "3.19.1"
}
]
]
}

View File

@ -1,3 +0,0 @@
module.exports = {
extends: '@urbit'
};

View File

@ -1 +0,0 @@
tmp

View File

@ -1,41 +0,0 @@
# Urbit API in JavaScript
This package simplifies the process of working with Urbit's APIs into fluent, typed functions organized by app. Pairs well with `@urbit/http-api`. Compare:
Without:
```ts
import UrbitInterface from '@urbit/http-api';
const api: UrbitInterface = useApi();
api.poke({
app: 'settings-store',
mark: 'settings-event',
json: {
'put-entry': {
'bucket-key': bucket,
'entry-key': key,
'value': value
}
}
});
```
With:
```ts
import UrbitInterface from '@urbit/http-api';
import { settings } from '@urbit/api';
const api: UrbitInterface = useApi();
api.poke(settings.putEntry(bucket, key, value));
```
You may import single functions
```ts
import { putEntry } from '@urbit/api';
```
or whole apps:
```ts
import { settings } from '@urbit/api';
```
This package also provides types and utilities for working with Urbit's internal data structures, such as Nouns, Das, Tas, and so forth.
This package was originally developed as part of Tlon's Landscape client and therefore the best reference material exists [there](https://github.com/urbit/urbit/tree/master/pkg/interface/src).

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,109 +0,0 @@
import { Patp, Poke, Scry } from '../lib';
import {
Contact,
ContactUpdateAdd,
ContactUpdateEdit,
ContactUpdateRemove,
ContactEditField,
ContactShare,
ContactUpdate,
ContactUpdateAllowShips,
ContactUpdateAllowGroup,
ContactUpdateSetPublic
} from './types';
export const CONTACT_UPDATE_VERSION = 0;
const storeAction = <T extends ContactUpdate>(data: T, version: number = CONTACT_UPDATE_VERSION): Poke<T> => ({
app: 'contact-store',
mark: `contact-update-${version}`,
json: data
});
export { storeAction as contactStoreAction };
export const addContact = (ship: Patp, contact: Contact): Poke<ContactUpdateAdd> => {
contact['last-updated'] = Date.now();
return storeAction({
add: { ship, contact }
});
};
export const removeContact = (ship: Patp): Poke<ContactUpdateRemove> =>
storeAction({
remove: { ship }
});
export const share = (recipient: Patp, version: number = CONTACT_UPDATE_VERSION): Poke<ContactShare> => ({
app: 'contact-push-hook',
mark: 'contact-share',
json: { share: recipient }
});
export const editContact = (
ship: Patp,
editField: ContactEditField
): Poke<ContactUpdateEdit> =>
storeAction({
edit: {
ship,
'edit-field': editField,
timestamp: Date.now()
}
});
export const allowShips = (
ships: Patp[]
): Poke<ContactUpdateAllowShips> => storeAction({
allow: {
ships
}
});
export const allowGroup = (
ship: string,
name: string
): Poke<ContactUpdateAllowGroup> => storeAction({
allow: {
group: { ship, name }
}
});
export const setPublic = (
setPublic: any
): Poke<ContactUpdateSetPublic> => {
return storeAction({
'set-public': setPublic
});
};
export const retrieve = (
ship: string
) => {
const resource = { ship, name: '' };
return {
app: 'contact-pull-hook',
mark: 'pull-hook-action',
json: {
add: {
resource,
ship
}
}
};
};
export const fetchIsAllowed = (
entity: string,
name: string,
ship: string,
personal: boolean
): Scry => {
const isPersonal = personal ? 'true' : 'false';
return {
app: 'contact-store',
path: `/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
};
};

View File

@ -1,82 +0,0 @@
import { Path, Patp } from '../lib';
import { Resource } from '../groups';
export type ContactUpdate =
| ContactUpdateAdd
| ContactUpdateRemove
| ContactUpdateEdit
| ContactUpdateInitial
| ContactUpdateAllowGroup
| ContactUpdateAllowShips
| ContactUpdateSetPublic;
export interface ContactUpdateAdd {
add: {
ship: Patp;
contact: Contact;
};
}
export interface ContactUpdateRemove {
remove: {
ship: Patp;
};
}
export interface ContactUpdateEdit {
edit: {
ship: Patp;
'edit-field': ContactEditField;
timestamp: number;
};
}
export interface ContactUpdateAllowShips {
allow: {
ships: Patp[];
}
}
export interface ContactUpdateAllowGroup {
allow: {
group: Resource;
}
}
export interface ContactUpdateSetPublic {
'set-public': boolean;
}
export interface ContactShare {
share: Patp;
}
export interface ContactUpdateInitial {
initial: Rolodex;
}
export type Rolodex = {
[p in Patp]: Contact;
};
export type Contacts = Rolodex;
export interface Contact {
nickname: string;
bio: string;
status: string;
color: string;
avatar: string | null;
cover: string | null;
groups: Path[];
'last-updated': number;
}
type ContactKeys = keyof Contact;
export type ContactEditFieldPrim = Exclude<ContactKeys, 'groups' | 'last-updated'>;
export type ContactEditField = Partial<Pick<Contact, ContactEditFieldPrim>> & {
'add-group'?: Resource;
'remove-group'?: Resource;
};

View File

@ -1,8 +0,0 @@
declare module 'urbit-ob' {
/**
* Convert a @p-encoded string to a decimal-encoded string.
*/
function patp2dec(name: string): string
}

View File

@ -1,2 +0,0 @@
export * from './lib';
export * from './types';

View File

@ -1,65 +0,0 @@
import { Poke, Scry } from '../lib';
import { Chad } from './types';
export function chadIsRunning(chad: Chad) {
return 'glob' in chad || 'site' in chad;
}
export const scryCharges: Scry = {
app: 'docket',
path: '/charges'
};
export const scryDockets: Scry = {
app: 'docket',
path: '/dockets'
};
export const scryTreaties: Scry = {
app: 'treaty',
path: '/treaties'
};
export const scryDefaultAlly: Scry = {
app: 'treaty',
path: '/default-ally'
};
export const scryAllies: Scry = {
app: 'treaty',
path: '/allies'
};
export const scryAllyTreaties = (ship: string): Scry => ({
app: 'treaty',
path: `/treaties/${ship}`
});
/**
* Uninstall a desk, and remove docket
*/
export function docketUninstall(desk: string): Poke<string> {
return {
app: 'docket',
mark: 'docket-uninstall',
json: desk
};
}
export function docketInstall(ship: string, desk: string): Poke<any> {
return {
app: 'docket',
mark: 'docket-install',
json: `${ship}/${desk}`
};
}
export function allyShip(ship: string): Poke<any> {
return {
app: 'treaty',
mark: 'ally-update-0',
json: {
add: ship
}
};
}

View File

@ -1,135 +0,0 @@
import { Cass } from '../hood';
export type DeskStatus = 'active' | 'suspended';
export type DocketHref = DocketHrefSite | DocketHrefGlob;
export interface DocketHrefGlob {
glob: {
base: string;
}
}
export interface DocketHrefSite {
site: string;
}
export interface Docket {
title: string;
info?: string;
color: string;
href: DocketHref;
website: string;
license: string;
version: string;
image?: string;
}
export interface Charge extends Docket {
chad: Chad;
}
export type Chad = HungChad | GlobChad | SiteChad | InstallChad | SuspendChad;
export interface HungChad {
hung: string;
}
export interface GlobChad {
glob: null;
}
export interface SiteChad {
site: null;
}
export interface InstallChad {
install: null;
}
export interface SuspendChad {
suspend: null;
}
export interface Treaty extends Docket {
ship: string;
desk: string;
cass: Cass;
hash: string;
}
export interface Charges {
[desk: string]: Charge;
}
export interface Treaties {
[ref: string]: Treaty;
}
export type Charter = string[];
export interface Allies {
[ship: string]: Charter;
}
export interface Provider {
shipName: string;
nickname?: string;
status?: string;
}
export type ChargeUpdate = ChargeUpdateInitial | ChargeUpdateAdd | ChargeUpdateDel;
export interface ChargeUpdateInitial {
initial: {
[desk: string]: Charge;
}
}
export interface ChargeUpdateAdd {
'add-charge': {
desk: string;
charge: Charge;
}
}
export interface ChargeUpdateDel {
'del-charge': string;
}
export type AllyUpdate = AllyUpdateIni | AllyUpdateAdd | AllyUpdateDel | AllyUpdateNew;
export interface AllyUpdateIni {
ini: {
[ship: string]: string[];
}
}
export interface AllyUpdateAdd {
add: string;
}
export interface AllyUpdateDel {
del: string;
}
export interface AllyUpdateNew {
new: {
ship: string;
alliance: string[];
}
}
export type TreatyUpdate = TreatyUpdateIni | TreatyUpdateAdd | TreatyUpdateDel;
export interface TreatyUpdateIni {
ini: {
[foreignDesk: string]: Treaty;
}
}
export interface TreatyUpdateAdd {
add: Treaty;
}
export interface TreatyUpdateDel {
del: string;
}

View File

@ -1,2 +0,0 @@
export * from './lib';
export * from './types';

View File

@ -1,534 +0,0 @@
import { GroupPolicy, makeResource, Resource, resourceFromPath } from '../groups';
import { decToUd, deSig, unixToDa, Scry } from '../lib';
import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types';
import { Content, GraphChildrenPoke, GraphNode, GraphNodePoke, Post } from './types';
import { patp2dec } from 'urbit-ob';
export const GRAPH_UPDATE_VERSION = 3;
export const createBlankNodeWithChildPost = (
ship: PatpNoSig,
parentIndex = '',
childIndex = '',
contents: Content[]
): GraphNodePoke => {
const date = unixToDa(Date.now()).toString();
const nodeIndex = parentIndex + '/' + date;
const childGraph: GraphChildrenPoke = {};
childGraph[childIndex] = {
post: {
author: `~${ship}`,
index: nodeIndex + '/' + childIndex,
'time-sent': Date.now(),
contents,
hash: null,
signatures: []
},
children: null
};
return {
post: {
author: `~${ship}`,
index: nodeIndex,
'time-sent': Date.now(),
contents: [],
hash: null,
signatures: []
},
children: childGraph
};
};
export const markPending = (nodes: any): any => {
Object.keys(nodes).forEach((key) => {
nodes[key].post.author = deSig(nodes[key].post.author);
nodes[key].post.pending = true;
if (nodes[key].children) {
nodes[key].children = markPending(nodes[key].children);
}
});
return nodes;
};
export const createPost = (
ship: PatpNoSig,
contents: Content[],
parentIndex = '',
childIndex = 'DATE_PLACEHOLDER'
): Post => {
if (childIndex === 'DATE_PLACEHOLDER') {
childIndex = unixToDa(Date.now()).toString();
}
return {
author: `~${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;
}
const storeAction = <T>(data: T, version: number = GRAPH_UPDATE_VERSION): Poke<T> => ({
app: 'graph-store',
mark: `graph-update-${version}`,
json: data
});
export { storeAction as graphStoreAction };
const viewAction = <T>(threadName: string, action: T): Thread<T> => ({
inputMark: 'graph-view-action',
outputMark: 'json',
threadName,
body: action
});
export { viewAction as graphViewAction };
const hookAction = <T>(data: T, version: number = GRAPH_UPDATE_VERSION): Poke<T> => ({
app: 'graph-push-hook',
mark: `graph-update-${version}`,
json: data
});
const dmAction = <T>(data: T): Poke<T> => ({
app: 'dm-hook',
mark: 'dm-hook-action',
json: data
});
export { hookAction as graphHookAction };
export const createManagedGraph = (
ship: PatpNoSig,
name: string,
title: string,
description: string,
group: Path,
mod: string
): Thread<any> => {
const associated = { group: resourceFromPath(group) };
const resource = makeResource(`~${ship}`, name);
return viewAction('graph-create', {
create: {
resource,
title,
description,
associated,
module: mod,
mark: moduleToMark(mod)
}
});
};
export const createUnmanagedGraph = (
ship: PatpNoSig,
name: string,
title: string,
description: string,
policy: Enc<GroupPolicy>,
mod: string
): Thread<any> => viewAction('graph-create', {
create: {
resource: makeResource(`~${ship}`, name),
title,
description,
associated: { policy },
module: mod,
mark: moduleToMark(mod)
}
});
export const joinGraph = (
ship: Patp,
name: string
): Thread<any> => viewAction('graph-join', {
join: {
resource: makeResource(ship, name),
ship
}
});
export const deleteGraph = (
ship: PatpNoSig,
name: string
): Thread<any> => viewAction('graph-delete', {
'delete': {
resource: makeResource(`~${ship}`, name)
}
});
export const leaveGraph = (
ship: Patp,
name: string
): Thread<any> => viewAction('graph-leave', {
'leave': {
resource: makeResource(ship, name)
}
});
export const groupifyGraph = (
ship: Patp,
name: string,
toPath?: string
): Thread<any> => {
const resource = makeResource(ship, name);
const to = toPath && resourceFromPath(toPath);
return viewAction('graph-groupify', {
groupify: {
resource,
to
}
});
};
export const evalCord = (
cord: string
): Thread<any> => {
return ({
inputMark: 'graph-view-action',
outputMark: 'tang',
threadName: 'graph-eval',
body: {
eval: cord
}
});
};
export const addGraph = (
ship: Patp,
name: string,
graph: any,
mark: any
): Poke<any> => {
return storeAction({
'add-graph': {
resource: { ship, name },
graph,
mark
}
});
};
export const addNodes = (
ship: Patp,
name: string,
nodes: Object
): Thread<any> => ({
inputMark: `graph-update-${GRAPH_UPDATE_VERSION}`,
outputMark: 'graph-view-action',
threadName: 'graph-add-nodes',
body: {
'add-nodes': {
resource: { ship, name },
nodes
}
}
});
export const addPost = (
ship: Patp,
name: string,
post: Post
): Thread<any> => {
const nodes: Record<string, GraphNode> = {};
nodes[post.index] = {
post,
children: null
};
return addNodes(ship, name, nodes);
};
export const addNode = (
ship: Patp,
name: string,
node: GraphNodePoke
): Thread<any> => {
const nodes: Record<string, GraphNodePoke> = {};
nodes[node.post.index] = node;
return addNodes(ship, name, nodes);
};
export const createGroupFeed = (
group: Resource,
vip: any = ''
): Thread<any> => ({
inputMark: 'graph-view-action',
outputMark: 'resource',
threadName: 'graph-create-group-feed',
body: {
'create-group-feed': {
resource: group,
vip
}
}
});
export const disableGroupFeed = (
group: Resource
): Thread<any> => ({
inputMark: 'graph-view-action',
outputMark: 'json',
threadName: 'graph-disable-group-feed',
body: {
'disable-group-feed': {
resource: group
}
}
});
/**
* Set dm-hook to screen new DMs or not
*
*/
export const setScreen = (screen: boolean): Poke<any> => dmAction({ screen });
/**
* Accept a pending DM request
*
* @param ship the ship to accept
*/
export const acceptDm = (ship: string) => dmAction({
accept: ship
});
/**
* Decline a pending DM request
*
* @param ship the ship to accept
*/
export const declineDm = (ship: string) => dmAction({
decline: ship
});
/**
* Remove posts from a set of indices
*
*/
export const removePosts = (
ship: Patp,
name: string,
indices: string[]
): Poke<any> => hookAction({
'remove-posts': {
resource: { ship, name },
indices
}
});
/**
* Remove a DM message from our inbox
*
* @remarks
* This does not remove the message from the recipients inbox
*/
export const removeDmMessage = (
our: Patp,
index: string
): Poke<any> => ({
app: 'graph-store',
mark: `graph-update-${GRAPH_UPDATE_VERSION}`,
json: {
'remove-posts': {
resource: { ship: our, name: 'dm-inbox' },
indices: [index]
}
}
});
/**
* Send a DM to a ship
*
* @param our sender
* @param ship recipient
* @param contents contents of message
*/
export const addDmMessage = (our: PatpNoSig, ship: Patp, contents: Content[]): Poke<any> => {
const post = createPost(our, contents, `/${patp2dec(ship)}`);
const node: GraphNode = {
post,
children: null
};
return {
app: 'dm-hook',
mark: `graph-update-${GRAPH_UPDATE_VERSION}`,
json: {
'add-nodes': {
resource: { ship: `~${our}`, name: 'dm-inbox' },
nodes: {
[post.index]: node
}
}
}
};
};
const encodeIndex = (idx: string) => idx.split('/').map(decToUd).join('/');
/**
* Fetch all graph keys
*/
export const getKeys = (): Scry => ({
app: 'graph-store',
path: '/keys'
});
/**
* Fetch newest (larger keys) nodes in a graph under some index
*
* @param ship ship of graph
* @param name name of graph
* @param count number of nodes to load
* @param index index to query
*/
export const getNewest = (
ship: string,
name: string,
count: number,
index = ''
): Scry => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/siblings` +
`/newest/lone/${count}${encodeIndex(index)}`
});
/**
* Fetch nodes in a graph that are older (key is smaller) and direct
* siblings of some index
*
* @param ship ship of graph
* @param name name of graph
* @param count number of nodes to load
* @param index index to query
*/
export const getOlderSiblings = (
ship: string,
name: string,
count: number,
index: string
): Scry => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/siblings/older/lone/${count}${encodeIndex(index)}`
});
/**
* Fetch nodes in a graph that are younger (key is larger) and direct
* siblings of some index
*
* @param ship ship of graph
* @param name name of graph
* @param count number of nodes to load
* @param index index to query
*/
export const getYoungerSiblings = (
ship: string,
name: string,
count: number,
index: string
): Scry => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/siblings/newer/lone/${count}${encodeIndex(index)}`
});
/**
* Fetch all nodes in a graph under some index, without loading children
*
* @param ship ship of graph
* @param name name of graph
* @param index index to query
*/
export const getShallowChildren = (ship: string, name: string, index = '') => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/children/lone/~/~${encodeIndex(index)}`
});
/**
* Fetch newest nodes in a graph as a flat map, including children,
* optionally starting at a specified key
*
* @param ship ship of graph
* @param name name of graph
* @param count number of nodes to load
* @param start key to start at
*
*/
export const getDeepOlderThan = (
ship: string,
name: string,
count: number,
start = ''
) => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/siblings` +
`/${start.length > 0 ? 'older' : 'newest'}` +
`/kith/${count}${encodeIndex(start)}`
});
/**
* Fetch a flat map of a nodes ancestors and firstborn children
*
* @param ship ship of graph
* @param name name of graph
* @param index index to query
*
*/
export const getFirstborn = (
ship: string,
name: string,
index: string
): Scry => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/firstborn${encodeIndex(index)}`
});
/**
* Fetch a single node, and all it's children
*
* @param ship ship of graph
* @param name name of graph
* @param index index to query
*
*/
export const getNode = (
ship: string,
name: string,
index: string
): Scry => ({
app: 'graph-store',
path: `/graph/${ship}/${name}/node/index/kith${encodeIndex(index)}`
});
/**
* Fetch entire graph
*
* @param ship ship of graph
* @param name name of graph
*
*/
export const getGraph = (
ship: string,
name: string
): Scry => ({
app: 'graph-store',
path: `/graph/${ship}/${name}`
});

View File

@ -1,94 +0,0 @@
import { Patp } from '../lib';
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
import { BigIntArrayOrderedMap } from '../lib/BigIntArrayOrderedMap';
export interface TextContent {
text: string;
}
export interface UrlContent {
url: string;
}
export interface CodeContent {
code: {
expression: string;
output: string[] | undefined;
}
}
export interface ReferenceContent {
reference: AppReference | GraphReference | GroupReference;
}
export interface GraphReference {
graph: {
graph: string;
group: string;
index: string;
}
}
export interface GroupReference {
group: string;
}
export interface AppReference {
app: {
ship: string;
desk: string;
path: string;
}
}
export interface MentionContent {
mention: string;
emphasis?: 'bold' | 'italic';
}
export type Content =
| TextContent
| UrlContent
| CodeContent
| ReferenceContent
| MentionContent;
export interface Post {
author: Patp;
contents: Content[];
hash: string | null;
index: string;
pending?: boolean;
signatures: string[];
'time-sent': number;
}
export interface GraphNodePoke {
post: Post;
children: GraphChildrenPoke | null;
}
export interface GraphChildrenPoke {
[k: string]: GraphNodePoke;
}
export interface GraphNode {
children: Graph | null;
post: Post;
}
export interface FlatGraphNode {
children: null;
post: Post;
}
export type Graph = BigIntOrderedMap<GraphNode>;
export type Graphs = { [rid: string]: Graph };
export type FlatGraph = BigIntArrayOrderedMap<FlatGraphNode>;
export type FlatGraphs = { [rid: string]: FlatGraph };
export type ThreadGraphs = {
[rid: string]: {
[index: string]: FlatGraph;
}
};

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,226 +0,0 @@
import { deSig } from '../index';
import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types';
import { Group, GroupPolicy, GroupPolicyDiff, GroupUpdateAddMembers, GroupUpdateAddTag, GroupUpdateChangePolicy, GroupUpdateRemoveGroup, GroupUpdateRemoveMembers, GroupUpdateRemoveTag, Resource, RoleTags, Tag } from './types';
import { GroupUpdate } from './update';
export const GROUP_UPDATE_VERSION = 0;
export const proxyAction = <T>(data: T, version: number = GROUP_UPDATE_VERSION): Poke<T> => ({
app: 'group-push-hook',
mark: `group-update-${version}`,
json: data
});
const storeAction = <T extends GroupUpdate>(data: T, version: number = GROUP_UPDATE_VERSION): Poke<T> => ({
app: 'group-store',
mark: `group-update-${version}`,
json: data
});
export { storeAction as groupStoreAction };
const viewAction = <T>(data: T): Poke<T> => ({
app: 'group-view',
mark: 'group-view-action',
json: data
});
export { viewAction as groupViewAction };
export const viewThread = <T>(thread: string, action: T): Thread<T> => ({
inputMark: 'group-view-action',
outputMark: 'json',
threadName: thread,
body: action
});
export const removeMembers = (
resource: Resource,
ships: PatpNoSig[]
): Poke<GroupUpdateRemoveMembers> => proxyAction({
removeMembers: {
resource,
ships
}
});
export const addTag = (
resource: Resource,
tag: Tag,
ships: Patp[]
): Poke<GroupUpdateAddTag> => proxyAction({
addTag: {
resource,
tag,
ships
}
});
export const removeTag = (
tag: Tag,
resource: Resource,
ships: PatpNoSig[]
): Poke<GroupUpdateRemoveTag> => proxyAction({
removeTag: {
tag,
resource,
ships
}
});
export const addMembers = (
resource: Resource,
ships: PatpNoSig[]
): Poke<GroupUpdateAddMembers> => proxyAction({
addMembers: {
resource,
ships
}
});
export const removeGroup = (
resource: Resource
): Poke<GroupUpdateRemoveGroup> => storeAction({
removeGroup: {
resource
}
});
export const changePolicy = (
resource: Resource,
diff: Enc<GroupPolicyDiff>
): Poke<Enc<GroupUpdateChangePolicy>> => proxyAction({
changePolicy: {
resource,
diff
}
});
export const join = (
ship: string,
name: string,
app: "groups" | "graph",
autojoin: boolean,
share: boolean
): Poke<any> => viewAction({
join: {
resource: makeResource(ship, name),
ship,
shareContact: share || false,
app,
autojoin
}
});
export const createGroup = (
name: string,
policy: Enc<GroupPolicy>,
title: string,
description: string
): Thread<any> => viewThread('group-create', {
create: {
name,
policy,
title,
description
}
});
export const deleteGroup = (
ship: string,
name: string
): Thread<any> => viewThread('group-delete', {
remove: makeResource(ship, name)
});
export const leaveGroup = (
ship: string,
name: string
): Thread<any> => viewThread('group-leave', {
leave: makeResource(ship, name)
});
export const invite = (
ship: string,
name: string,
ships: Patp[],
description: string
): Thread<any> => viewThread('group-invite', {
invite: {
resource: makeResource(ship, name),
ships,
description
}
});
export const abortJoin = (
resource: string
): Poke<any> => viewAction({
abort: resource
});
export const roleTags = ['janitor', 'moderator', 'admin'];
// TODO make this type better?
export const groupBunts = {
group: (): Group => ({ members: [], tags: { role: {} }, hidden: false, policy: groupBunts.policy() }),
policy: (): GroupPolicy => ({ open: { banned: [], banRanks: [] } })
};
export const joinError = ['no-perms', 'strange', 'abort'] as const;
export const joinResult = ['done', ...joinError] as const;
export const joinLoad = ['start', 'added', 'metadata'] as const;
export const joinProgress = [...joinLoad, ...joinResult] as const;
export function roleForShip(
group: Group,
ship: PatpNoSig
): RoleTags | undefined {
return roleTags.reduce((currRole, role) => {
const roleShips = group?.tags?.role?.[role];
return roleShips && roleShips.includes(ship) ? role : currRole;
}, undefined as RoleTags | undefined);
};
export function resourceFromPath(path: Path): Resource {
const [, , ship, name] = path.split('/');
return { ship, name };
}
export function makeResource(ship: string, name: string) {
return { ship, name };
}
export const isWriter = (group: Group, resource: string, ship: string) => {
const graph = group?.tags?.graph;
const writers: string[] | undefined = graph && (graph[resource] as any)?.writers;
const admins = group?.tags?.role?.admin ?? [];
if (typeof writers === 'undefined') {
return true;
} else {
return [...writers].includes(ship) || admins.includes(ship);
}
};
export function isChannelAdmin(
group: Group,
resource: string,
ship: string
): boolean {
const role = roleForShip(group, deSig(ship));
return (
isHost(resource, ship) ||
role === 'admin' ||
role === 'moderator'
);
}
export function isHost(
resource: string,
ship: string
): boolean {
const [, , host] = resource.split('/');
return ship === host;
}

View File

@ -1,2 +0,0 @@
export * from './update';
export * from './view';

View File

@ -1,175 +0,0 @@
import { PatpNoSig, Path, ShipRank, Enc } from '../lib';
import { roleTags } from './index';
export type RoleTags = typeof roleTags[number];
interface RoleTag {
tag: 'admin' | 'moderator' | 'janitor';
}
interface AppTag {
app: string;
resource: string;
tag: string;
}
export type Tag = AppTag | RoleTag;
export interface InvitePolicy {
invite: {
pending: PatpNoSig[];
};
}
export interface OpenPolicy {
open: {
banned: PatpNoSig[];
banRanks: ShipRank[];
};
}
export interface Resource {
name: string;
ship: PatpNoSig;
}
export type OpenPolicyDiff =
| AllowRanksDiff
| BanRanksDiff
| AllowShipsDiff
| BanShipsDiff;
export interface AllowRanksDiff {
allowRanks: ShipRank[];
}
export interface BanRanksDiff {
banRanks: ShipRank[];
}
export interface AllowShipsDiff {
allowShips: PatpNoSig[];
}
export interface BanShipsDiff {
banShips: PatpNoSig[];
}
export type InvitePolicyDiff = AddInvitesDiff | RemoveInvitesDiff;
export interface AddInvitesDiff {
addInvites: PatpNoSig[];
}
export interface RemoveInvitesDiff {
removeInvites: PatpNoSig[];
}
export interface ReplacePolicyDiff {
replace: GroupPolicy;
}
export type GroupPolicyDiff =
| { open: OpenPolicyDiff }
| { invite: InvitePolicyDiff }
| ReplacePolicyDiff;
export type GroupPolicy = OpenPolicy | InvitePolicy;
export interface TaggedShips {
[tag: string]: PatpNoSig[];
}
export interface Tags {
role: TaggedShips;
[app: string]: TaggedShips;
}
export interface Group {
members: PatpNoSig[];
tags: Tags;
policy: GroupPolicy;
hidden: boolean;
}
export type Groups = {
[p in Path]: Group;
};
export interface GroupUpdateInitial {
initial: Enc<Groups>;
}
export interface GroupUpdateAddGroup {
addGroup: {
resource: Resource;
policy: Enc<GroupPolicy>;
hidden: boolean;
};
}
export interface GroupUpdateAddMembers {
addMembers: {
ships: PatpNoSig[];
resource: Resource;
};
}
export interface GroupUpdateRemoveMembers {
removeMembers: {
ships: PatpNoSig[];
resource: Resource;
};
}
export interface GroupUpdateAddTag {
addTag: {
tag: Tag;
resource: Resource;
ships: PatpNoSig[];
};
}
export interface GroupUpdateRemoveTag {
removeTag: {
tag: Tag;
resource: Resource;
ships: PatpNoSig[];
};
}
export interface GroupUpdateChangePolicy {
changePolicy: { resource: Resource; diff: GroupPolicyDiff };
}
export interface GroupUpdateRemoveGroup {
removeGroup: {
resource: Resource;
};
}
export interface GroupUpdateExpose {
expose: {
resource: Resource;
};
}
export interface GroupUpdateInitialGroup {
initialGroup: {
resource: Resource;
group: Enc<Group>;
};
}
export type GroupUpdate =
| GroupUpdateInitial
| GroupUpdateAddGroup
| GroupUpdateAddMembers
| GroupUpdateRemoveMembers
| GroupUpdateAddTag
| GroupUpdateRemoveTag
| GroupUpdateChangePolicy
| GroupUpdateRemoveGroup
| GroupUpdateExpose
| GroupUpdateInitialGroup;
export type GroupAction = Omit<GroupUpdate, 'initialGroup' | 'initial'>;

View File

@ -1,30 +0,0 @@
import { joinError, joinProgress, joinResult } from ".";
import {Patp} from "../lib";
export type JoinError = typeof joinError[number];
export type JoinResult = typeof joinResult[number];
export type JoinProgress = typeof joinProgress[number];
export interface JoinRequest {
/**
* Whether to display the join request or not
*/
hidden: boolean;
/**
* Timestamp of when the request started
*/
started: number;
ship: Patp;
progress: JoinProgress;
shareContact: boolean;
autojoin: boolean;
app: 'graph' | 'groups';
invite: string[];
}
export interface JoinRequests {
[rid: string]: JoinRequest;
}

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,163 +0,0 @@
import { BigInteger } from 'big-integer';
import { Poke } from '../lib/types';
import {
HarkBin,
HarkBinId,
HarkBody,
HarkLid,
HarkPlace
} from './types';
import { decToUd } from '../lib';
export const harkAction = <T>(data: T): Poke<T> => ({
app: 'hark-store',
mark: 'hark-action',
json: data
});
const graphHookAction = <T>(data: T): Poke<T> => ({
app: 'hark-graph-hook',
mark: 'hark-graph-hook-action',
json: data
});
export { graphHookAction as harkGraphHookAction };
const groupHookAction = <T>(data: T): Poke<T> => ({
app: 'hark-group-hook',
mark: 'hark-group-hook-action',
json: data
});
export { groupHookAction as harkGroupHookAction };
export const actOnNotification = (
frond: string,
intTime: BigInteger,
bin: HarkBin
): Poke<unknown> =>
harkAction({
[frond]: {
time: decToUd(intTime.toString()),
bin
}
});
export const setMentions = (mentions: boolean): Poke<unknown> =>
graphHookAction({
'set-mentions': mentions
});
export const setWatchOnSelf = (watchSelf: boolean): Poke<unknown> =>
graphHookAction({
'set-watch-on-self': watchSelf
});
export const setDoNotDisturb = (dnd: boolean): Poke<unknown> =>
harkAction({
'set-dnd': dnd
});
export const addNote = (bin: HarkBin, body: HarkBody) =>
harkAction({
'add-note': {
bin,
body
}
});
export const archive = (bin: HarkBin, lid: HarkLid): Poke<unknown> =>
harkAction({
archive: {
lid,
bin
}
});
export const opened = harkAction({
opened: null
});
export const markCountAsRead = (place: HarkPlace): Poke<unknown> =>
harkAction({
'read-count': place
});
export const markEachAsRead = (
place: HarkPlace,
path: string
): Poke<unknown> =>
harkAction({
'read-each': {
place,
path
}
});
export const seen = () => harkAction({ seen: null });
export const readAll = harkAction({ 'read-all': null });
export const archiveAll = harkAction({ 'archive-all': null });
export const ignoreGroup = (group: string): Poke<unknown> =>
groupHookAction({
ignore: group
});
export const ignoreGraph = (graph: string, index: string): Poke<unknown> =>
graphHookAction({
ignore: {
graph,
index
}
});
export const listenGroup = (group: string): Poke<unknown> =>
groupHookAction({
listen: group
});
export const listenGraph = (graph: string, index: string): Poke<unknown> =>
graphHookAction({
listen: {
graph,
index
}
});
/**
* Read all graphs belonging to a particular group
*/
export const readGroup = (group: string) =>
harkAction({
'read-group': group
});
/**
* Read all unreads in a graph
*/
export const readGraph = (graph: string) =>
harkAction({
'read-graph': graph
});
export function harkBinToId(bin: HarkBin): HarkBinId {
const { place, path } = bin;
return `${place.desk}${place.path}${path}`;
}
export function harkBinEq(a: HarkBin, b: HarkBin): boolean {
return (
a.place.path === b.place.path &&
a.place.desk === b.place.desk &&
a.path === b.path
);
}
export function harkLidToId(lid: HarkLid): string {
if('time' in lid) {
return `archive-${lid.time}`;
}
return Object.keys(lid)[0];
}

View File

@ -1,58 +0,0 @@
export interface HarkStats {
count: number;
each: string[];
last: number;
}
export interface Timebox {
[binId: string]: Notification;
}
export type HarkContent = { ship: string; } | { text: string; };
export interface HarkBody {
title: HarkContent[];
time: number;
content: HarkContent[];
link: string;
binned: string;
}
export interface HarkPlace {
desk: string;
path: string;
}
export interface HarkBin {
path: string;
place: HarkPlace;
}
export type HarkLid =
{ unseen: null; }
| { seen: null; }
| { time: string; };
export type HarkBinId = string;
export interface Notification {
bin: HarkBin;
time: number;
body: HarkBody[];
}
export interface NotificationGraphConfig {
watchOnSelf: boolean;
mentions: boolean;
watching: WatchedIndex[]
}
export interface Unreads {
[path: string]: HarkStats;
}
interface WatchedIndex {
graph: string;
index: string;
}
export type GroupNotificationsConfig = string[];

View File

@ -1,2 +0,0 @@
export * from './lib';
export * from './types';

View File

@ -1,127 +0,0 @@
import { Poke, Scry } from '../lib';
import { Pike } from './types';
export const getPikes: Scry = {
app: 'hood',
path: '/kiln/pikes'
};
/**
* Install a foreign desk
*/
export function kilnInstall(
ship: string,
desk: string,
local?: string
): Poke<any> {
return {
app: 'hood',
mark: 'kiln-install',
json: {
ship,
desk,
local: local || desk
}
};
}
/**
* Sync with a foreign desk
*/
export function kilnSync(
ship: string,
desk: string,
local?: string
): Poke<any> {
return {
app: 'hood',
mark: 'kiln-sync',
json: {
ship,
desk,
local: local || desk
}
};
}
/**
* Unsync with a foreign desk
*/
export function kilnUnsync(
ship: string,
desk: string,
local?: string
): Poke<any> {
return {
app: 'hood',
mark: 'kiln-unsync',
json: {
ship,
desk,
local: local || desk
}
};
}
/**
* Uninstall a desk
*/
export function kilnUninstall(
desk: string
): Poke<any> {
return {
app: 'hood',
mark: 'kiln-uninstall',
json: desk
};
}
export function kilnSuspend(
desk: string
): Poke<any> {
return {
app: 'hood',
mark: 'kiln-suspend',
json: desk
};
}
export function kilnRevive(
desk: string
): Poke<any> {
return {
app: 'hood',
mark: 'kiln-revive',
json: desk
};
}
export function kilnBump(): Poke<any> {
return {
app: 'hood',
mark: 'kiln-bump',
json: null,
};
}
export function kilnPause(desk: string) {
return {
app: 'hood',
mark: 'kiln-pause',
json: desk
};
}
export function kilnResume(desk: string) {
return {
app: 'hood',
mark: 'kiln-resume',
json: desk
};
}
export const scryLag: Scry = ({ app: 'hood', path: '/kiln/lag' });
export function getPikePublisher(pike: Pike) {
return pike.sync?.ship;
}

View File

@ -1,208 +0,0 @@
/**
* A pending commit, awaiting a future kelvin version
*/
interface Woof {
aeon: number;
weft: Weft;
}
interface Rein {
/**
* Agents not in manifest that should be running
*/
add: string[];
/**
* Agents in manifest that should not be running
*/
sub: string[];
}
export interface Rail {
/**
* Original publisher of desk, if available
*/
publisher: string | null;
/**
* Ship of foreign vat
*/
ship: string;
/**
* Desk of foreign vat
*/
desk: string;
/**
* Aeon (version number) that we currently have synced
*/
aeon: number;
next: Woof[];
paused: boolean;
}
/**
* A tracker of a foreign {@link Vat}
*
*/
export interface Arak {
rein: Rein;
rail: Rail | null;
}
/**
* A component's kelvin version
*/
export interface Weft {
/**
* Name of the component
*
* @remarks
* Usually %zuse, %hoon, or %lull
*/
name: string;
/**
* Kelvin version
*
*/
kelvin: number;
}
export interface KilnDiffBlock {
block: {
desk: string;
arak: Arak;
weft: Weft;
blockers: string[];
};
}
export interface KilnDiffReset {
reset: {
desk: string;
arak: Arak;
};
}
export interface KilnDiffMerge {
merge: {
desk: string;
arak: Arak;
};
}
export interface KilnDiffMergeSunk {
'merge-sunk': {
desk: string;
arak: Arak;
tang: string;
};
}
export interface KilnDiffMergeFail {
'merge-fail': {
desk: string;
arak: Arak;
tang: string;
};
}
export type KilnDiff =
| KilnDiffBlock
| KilnDiffReset
| KilnDiffMerge
| KilnDiffMergeSunk
| KilnDiffMergeFail;
/**
* Cases for revision
*
*/
export interface Cass {
/**
* Revision number
*/
ud: number;
/**
* Timestamp of revision, as stringifed `@da`
*
* @remarks
* If \@da is outside valid positive unix timestamp, value will be zero
*/
da: string;
}
/**
* A local desk installation
*/
export interface Vat {
/**
* Desk that this Vat describes
*/
desk: string;
/**
* Hash of the desk, rendered as `@uv`
*
* @remarks
* Equivalent to
* ```hoon
* .^(@uv %cz /=desk=)
* ```
*/
hash: string;
/**
* Current revision
*/
cass: Cass;
/**
* Foreign sync
*/
arak: Arak;
}
export interface Vats {
[desk: string]: Vat;
}
/**
* TODO: crisp one-liner describing a Pike
*/
export interface Pike {
/**
* Hash of the desk, rendered as `@uv`
*
* @remarks
* Equivalent to
* ```hoon
* .^(@uv %cz /=desk=)
* ```
*/
hash: string;
sync: {
/**
* Source desk for this Pike
*/
desk: string;
/**
* Source ship for this Pike
*/
ship: string;
} | null;
/**
* {@link Weft}s associated with this Pike
*/
wefts: Weft[];
/**
* how live is this pike?
*
* live - app is running
* held - app is not running, but is trying to run. this state can be entered
* in two main ways:
* - when installing an app but it hasn't finished downloading (or it did
* but failed to install for some reason)
* - when user forced a kelvin upgrade by suspending desks.
* dead - app is not running
*/
zest: "live" | "dead" | "held";
}
export interface Pikes {
[desk: string]: Pike;
}

View File

@ -1,26 +0,0 @@
export * from './contacts';
export * as contacts from './contacts';
export * from './graph';
export * as graph from './graph';
export * from './groups';
export * as groups from './groups';
export * from './hark';
export * as hark from './hark';
export * from './invite';
// this conflicts with /groups/lib invite
// export * as invite from './invite';
export * from './metadata';
export * as metadata from './metadata';
export * from './settings';
export * as settings from './settings';
export * from './s3';
export * as s3 from './s3';
export * from './lib';
export * from './lib/BigIntOrderedMap';
export * from './lib/BigIntArrayOrderedMap';
export * as hood from './hood';
export * from './hood';
export * as docket from './docket';
export * from './docket';
export * as term from './term';
export * from './term';

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,28 +0,0 @@
import { Poke, Serial } from "../lib";
import { InviteUpdate, InviteUpdateAccept, InviteUpdateDecline } from "./types";
export const inviteAction = <T extends InviteUpdate>(data: T): Poke<T> => ({
app: 'invite-store',
mark: 'invite-action',
json: data
});
export const accept = (
app: string,
uid: Serial
): Poke<InviteUpdateAccept> => inviteAction({
accept: {
term: app,
uid
}
});
export const decline = (
app: string,
uid: Serial
): Poke<InviteUpdateDecline> => inviteAction({
decline: {
term: app,
uid
}
});

View File

@ -1,75 +0,0 @@
import { Serial, PatpNoSig, Path } from '../lib';
import { Resource } from "../groups";
export type InviteUpdate =
InviteUpdateInitial
| InviteUpdateCreate
| InviteUpdateDelete
| InviteUpdateInvite
| InviteUpdateAccept
| InviteUpdateAccepted
| InviteUpdateDecline;
export interface InviteUpdateAccept {
accept: {
term: string;
uid: Serial;
}
}
export interface InviteUpdateInitial {
initial: Invites;
}
export interface InviteUpdateCreate {
create: {
term: string;
};
}
export interface InviteUpdateDelete {
delete: {
term: string;
};
}
export interface InviteUpdateInvite {
invite: {
term: string;
uid: Serial;
invite: Invite;
};
}
export interface InviteUpdateAccepted {
accepted: {
term: string;
uid: Serial;
};
}
export interface InviteUpdateDecline {
decline: {
term: string;
uid: Serial;
};
}
// actual datastructures
export type Invites = {
[p in Path]: AppInvites;
};
export type AppInvites = {
[s in Serial]: Invite;
};
export interface Invite {
app: string;
recipient: PatpNoSig;
resource: Resource;
ship: PatpNoSig;
text: string;
}

View File

@ -1,152 +0,0 @@
import produce, { immerable, castDraft, setAutoFreeze, enablePatches } from 'immer';
import bigInt, { BigInteger } from 'big-integer';
setAutoFreeze(false);
enablePatches();
export function stringToArr(str: string) {
return str.split('/').slice(1).map((ind) => {
return bigInt(ind);
});
}
export function arrToString(arr: BigInteger[]) {
let string = '';
arr.forEach((key) => {
string = string + `/${key.toString()}`;
});
return string;
}
function sorted(a: BigInteger[], b: BigInteger[], reversed = false) {
const getSort = sortBigIntArr(a, b);
if (reversed) {
return getSort * -1;
} else {
return getSort;
}
}
export function sortBigIntArr(a: BigInteger[], b: BigInteger[]) {
const aLen = a.length;
const bLen = b.length;
const aCop = a.slice(0);
const bCop = b.slice(0);
aCop.reverse();
bCop.reverse();
let i = 0;
while (i < aLen && i < bLen) {
if (aCop[i].lt(bCop[i])) {
return 1;
} else if (aCop[i].gt(bCop[i])) {
return -1;
} else {
i++;
}
}
return bLen - aLen;
}
export class BigIntArrayOrderedMap<V> implements Iterable<[BigInteger[], V]> {
root: Record<string, V> = {}
cachedIter: [BigInteger[], V][] | null = null;
[immerable] = true;
reversed = false;
constructor(items: [BigInteger[], V][] = [], reversed = false) {
items.forEach(([key, val]) => {
this.set(key, val);
});
this.reversed = reversed;
}
get size() {
return Object.keys(this.root).length;
}
get(key: BigInteger[]) {
return this.root[arrToString(key)] ?? null;
}
gas(items: [BigInteger[], V][]) {
return produce(this, (draft) => {
items.forEach(([key, value]) => {
draft.root[arrToString(key)] = castDraft(value);
});
draft.generateCachedIter();
},
(patches) => {
// console.log(`gassed with ${JSON.stringify(patches, null, 2)}`);
});
}
set(key: BigInteger[], value: V) {
return produce(this, (draft) => {
draft.root[arrToString(key)] = castDraft(value);
draft.cachedIter = null;
});
}
clear() {
return produce(this, (draft) => {
draft.cachedIter = [];
draft.root = {};
});
}
has(key: BigInteger[]) {
return arrToString(key) in this.root;
}
delete(key: BigInteger[]) {
const result = produce(this, (draft) => {
delete draft.root[arrToString(key)];
draft.cachedIter = null;
});
return result;
}
[Symbol.iterator](): IterableIterator<[BigInteger[], V]> {
let idx = 0;
const result = this.generateCachedIter();
return {
[Symbol.iterator]: this[Symbol.iterator],
next: (): IteratorResult<[BigInteger[], V]> => {
if (idx < result.length) {
return { value: result[idx++], done: false };
}
return { done: true, value: null };
}
};
}
peekLargest() {
const sorted = Array.from(this);
return sorted[0] as [BigInteger[], V] | null;
}
peekSmallest() {
const sorted = Array.from(this);
return sorted[sorted.length - 1] as [BigInteger[], V] | null;
}
keys() {
return Array.from(this).map(([k,v]) => k);
}
generateCachedIter() {
if(this.cachedIter) {
return [...this.cachedIter];
}
const result = Object.keys(this.root).map((key) => {
return [stringToArr(key), this.root[key]] as [BigInteger[], V];
}).sort(([a], [b]) => sorted(a, b, this.reversed));
this.cachedIter = result;
return [...result];
}
}

View File

@ -1,117 +0,0 @@
import produce, { immerable, castDraft, setAutoFreeze, enablePatches } from 'immer';
import bigInt, { BigInteger } from 'big-integer';
setAutoFreeze(false);
enablePatches();
function sortBigInt(a: BigInteger, b: BigInteger) {
if (a.lt(b)) {
return 1;
} else if (a.eq(b)) {
return 0;
} else {
return -1;
}
}
export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
root: Record<string, V> = {}
cachedIter: [BigInteger, V][] | null = null;
[immerable] = true;
constructor(items: [BigInteger, V][] = []) {
items.forEach(([key, val]) => {
this.set(key, val);
});
}
get size() {
if(this.cachedIter) {
return this.cachedIter.length;
}
return this.generateCachedIter().length;
}
get(key: BigInteger) {
return this.root[key.toString()] ?? null;
}
gas(items: [BigInteger, V][]) {
return produce(this, (draft) => {
items.forEach(([key, value]) => {
draft.root[key.toString()] = castDraft(value);
});
draft.cachedIter = null;
},
(patches) => {
// console.log(`gassed with ${JSON.stringify(patches, null, 2)}`);
});
}
set(key: BigInteger, value: V) {
return produce(this, (draft) => {
draft.root[key.toString()] = castDraft(value);
draft.cachedIter = null;
});
}
clear() {
return produce(this, (draft) => {
draft.cachedIter = [];
draft.root = {};
});
}
has(key: BigInteger) {
return key.toString() in this.root;
}
delete(key: BigInteger) {
const result = produce(this, (draft) => {
delete draft.root[key.toString()];
draft.cachedIter = null;
});
return result;
}
[Symbol.iterator](): IterableIterator<[BigInteger, V]> {
let idx = 0;
const result = this.generateCachedIter();
return {
[Symbol.iterator]: this[Symbol.iterator],
next: (): IteratorResult<[BigInteger, V]> => {
if (idx < result.length) {
return { value: result[idx++], done: false };
}
return { done: true, value: null };
}
};
}
peekLargest() {
const sorted = Array.from(this);
return sorted[0] as [BigInteger, V] | null;
}
peekSmallest() {
const sorted = Array.from(this);
return sorted[sorted.length - 1] as [BigInteger, V] | null;
}
keys() {
return Array.from(this).map(([k,v]) => k);
}
generateCachedIter() {
if(this.cachedIter) {
return [...this.cachedIter];
}
const result = Object.keys(this.root).map((key) => {
const num = bigInt(key);
return [num, this.root[key]] as [BigInteger, V];
}).sort(([a], [b]) => sortBigInt(a,b));
this.cachedIter = result;
return [...result];
}
}

View File

@ -1,2 +0,0 @@
export * from './lib';
export * from './types';

View File

@ -1,259 +0,0 @@
import bigInt, { BigInteger } from "big-integer";
import { Resource } from "../groups/types";
import { Post, GraphNode } from "../graph/types";
const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud` ~1970.1.1
const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1
function chunk<T>(arr: T[], size: number): T[][] {
let chunk: T[] = [];
let newArray = [chunk];
for (let i = 0;i < arr.length;i++) {
if (chunk.length < size) {
chunk.push(arr[i])
} else {
chunk = [arr[i]]
newArray.push(chunk)
}
}
return newArray;
}
function dropWhile<T>(arr: T[], pred: (x: T) => boolean): T[] {
const newArray = arr.slice();
for (const item of arr) {
if (pred(item)) {
newArray.shift();
} else {
return newArray;
}
}
return newArray;
}
/**
* Given a bigint representing an urbit date, returns a unix timestamp.
*
* @param {BigInteger} da The urbit date
*
* @return {number} The unix timestamp
*/
export function daToUnix(da: BigInteger): number {
// ported from +time:enjs:format in hoon.hoon
const offset = DA_SECOND.divide(bigInt(2000));
const epochAdjusted = offset.add(da.subtract(DA_UNIX_EPOCH));
return Math.round(
epochAdjusted.multiply(bigInt(1000)).divide(DA_SECOND).toJSNumber()
);
}
/**
* Given a unix timestamp, returns a bigint representing an urbit date
*
* @param {number} unix The unix timestamp
*
* @return {BigInteger} The urbit date
*/
export function unixToDa(unix: number): BigInteger {
const timeSinceEpoch = bigInt(unix).multiply(DA_SECOND).divide(bigInt(1000));
return DA_UNIX_EPOCH.add(timeSinceEpoch);
}
export function makePatDa(patda: string): BigInteger {
return bigInt(udToDec(patda));
}
export function udToDec(ud: string): string {
return ud.replace(/\./g, "");
}
export function decToUd(str: string): string {
const transform = chunk(str.split('').reverse(), 3)
.map(group => group.reverse().join(''))
.reverse()
.join('.')
return transform.replace(/^[0\.]+/g, '');
}
export function resourceAsPath(resource: Resource): string {
const { name, ship } = resource;
return `/ship/~${ship}/${name}`;
}
export function uuid(): string {
let str = "0v";
str += Math.ceil(Math.random() * 8) + ".";
for (let i = 0; i < 5; i++) {
let _str = Math.ceil(Math.random() * 10000000).toString(32);
_str = ("00000" + _str).substr(-5, 5);
str += _str + ".";
}
return str.slice(0, -1);
}
/*
Goes from:
~2018.7.17..23.15.09..5be5 // urbit @da
To:
(javascript Date object)
*/
export function daToDate(st: string): Date {
const dub = function (n: string) {
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
};
const da = st.split("..");
const bigEnd = da[0].split(".");
const lilEnd = da[1].split(".");
const ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(
lilEnd[0]
)}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
return new Date(ds);
}
/*
Goes from:
(javascript Date object)
To:
~2018.7.17..23.15.09..5be5 // urbit @da
*/
export function dateToDa(d: Date, mil: boolean = false): string {
const fil = function (n: number) {
return n >= 10 ? n : "0" + n;
};
return (
`~${d.getUTCFullYear()}.` +
`${d.getUTCMonth() + 1}.` +
`${fil(d.getUTCDate())}..` +
`${fil(d.getUTCHours())}.` +
`${fil(d.getUTCMinutes())}.` +
`${fil(d.getUTCSeconds())}` +
`${mil ? "..0000" : ""}`
);
}
export function preSig(ship: string): string {
if (!ship) {
return '';
}
if (ship.trim().startsWith('~')) {
return ship.trim();
}
return '~'.concat(ship.trim());
}
export function deSig(ship: string): string | null {
if (!ship) {
return null;
}
return ship.replace("~", "");
}
// trim patps to match dojo, chat-cli
export function cite(ship: string) {
let patp = ship,
shortened = '';
if (patp === null || patp === '') {
return null;
}
if (patp.startsWith('~')) {
patp = patp.substr(1);
}
// comet
if (patp.length === 56) {
shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56);
return shortened;
}
// moon
if (patp.length === 27) {
shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27);
return shortened;
}
return `~${patp}`;
}
export function uxToHex(ux: string) {
if (ux.length > 2 && ux.substr(0, 2) === '0x') {
const value = ux.substr(2).replace('.', '').padStart(6, '0');
return value;
}
const value = ux.replace('.', '').padStart(6, '0');
return value;
}
export const hexToUx = (hex: string): string => {
const nonZeroChars = dropWhile(hex.split(''), y => y === '0');
const ux = chunk(nonZeroChars.reverse(), 4).map(x => {
return x.reverse().join('');
}).reverse().join('.') || '0';
return `0x${ux}`;
};
// encode the string into @ta-safe format, using logic from +wood.
// for example, 'some Chars!' becomes '~.some.~43.hars~21.'
//
export function stringToTa(str: string): string {
let out = "";
for (let i = 0; i < str.length; i++) {
const char = str[i];
let add = "";
switch (char) {
case " ":
add = ".";
break;
case ".":
add = "~.";
break;
case "~":
add = "~~";
break;
default:
const charCode = str.charCodeAt(i);
if (
(charCode >= 97 && charCode <= 122) || // a-z
(charCode >= 48 && charCode <= 57) || // 0-9
char === "-"
) {
add = char;
} else {
// TODO behavior for unicode doesn't match +wood's,
// but we can probably get away with that for now.
add = "~" + charCode.toString(16) + ".";
}
}
out = out + add;
}
return "~." + out;
}
export const buntPost = (): Post => ({
author: '',
contents: [],
hash: null,
index: '',
signatures: [],
'time-sent': 0
});
export function makeNodeMap(posts: Post[]): Record<string, GraphNode> {
const nodes: Record<string, GraphNode> = {};
posts.forEach((p: Post) => {
nodes[String(p.index)] = { children: null, post: p };
});
return nodes;
}

View File

@ -1,67 +0,0 @@
/**
* Martian embassy
*/
import { BigIntOrderedMap } from "./BigIntOrderedMap";
// an urbit style path rendered as string
export type Path = string;
// patp including leading sig
export type Patp = string;
// patp excluding leading sig
export type PatpNoSig = string;
// @uvH encoded string
export type Serial = string;
// jug from hoon
export type Jug<K,V> = Map<K,Set<V>>;
// name of app
export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph' | 'groups';
export type ShipRank = 'czar' | 'king' | 'duke' | 'earl' | 'pawn';
export type Action = 'poke' | 'subscribe' | 'ack' | 'unsubscribe' | 'delete';
export type SetElement<S> = S extends Set<(infer T)> ? T : never;
export type MapKey<M> = M extends Map<(infer K), any> ? K : never;
export type MapValue<M> = M extends Map<any, (infer V)> ? V : never;
/**
* Turns sets into arrays and maps into objects so we can send them over the wire
*/
export type Enc<S> =
S extends Set<any> ?
Enc<SetElement<S>>[] :
S extends Map<string, any> ?
{ [s: string]: Enc<MapValue<S>> } :
S extends object ?
{ [K in keyof S]: Enc<S[K]> } :
S extends BigIntOrderedMap<infer T> ?
{ [index: string]: T } :
S;
export type Mark = string;
export interface Poke<Action> {
ship?: string; // This should be handled by the http library, but is part of the spec
app: string;
mark: Mark;
json: Action;
}
export interface Scry {
app: string;
path: string;
}
export interface Thread<Action> {
inputMark: string;
outputMark: string;
threadName: string;
body: Action;
}

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,99 +0,0 @@
import { Path, Poke, uxToHex, PatpNoSig } from '../lib';
import { MdAppName, Association, Metadata, MetadataUpdate, MetadataUpdateAdd, MetadataUpdateRemove, MetadataEditField, MetadataUpdateEdit } from './types';
export const METADATA_UPDATE_VERSION = 2;
export const metadataAction = <T extends MetadataUpdate>(data: T, version: number = METADATA_UPDATE_VERSION): Poke<T> => ({
app: 'metadata-push-hook',
mark: `metadata-update-${version}`,
json: data
});
export const add = (
ship: PatpNoSig,
appName: MdAppName,
resource: Path,
group: Path,
title: string,
description: string,
dateCreated: string,
color: string,
moduleName: string
): Poke<MetadataUpdateAdd> => metadataAction({
add: {
group,
resource: {
resource,
'app-name': appName
},
metadata: {
title,
description,
color,
'date-created': dateCreated,
creator: `~${ship}`,
config: { graph: moduleName },
picture: '',
hidden: false,
preview: false,
vip: ''
}
}
});
export { add as metadataAdd };
export const remove = (
appName: MdAppName,
resource: string,
group: string
): Poke<MetadataUpdateRemove> => metadataAction<MetadataUpdateRemove>({
remove: {
group,
resource: {
resource,
'app-name': appName
}
}
});
export { remove as metadataRemove };
export const edit = (
association: Association,
edit: MetadataEditField
): Poke<MetadataUpdateEdit> => metadataAction<MetadataUpdateEdit>({
edit: {
group: association.group,
resource: {
resource: association.resource,
'app-name': association['app-name']
},
edit
}
});
export { edit as metadataEdit };
/**
* @deprecated use {@link edit} instead
*/
export const update = (
association: Association,
newMetadata: Partial<Metadata>
): Poke<MetadataUpdateAdd> => {
const metadata = { ...association.metadata, ...newMetadata };
metadata.color = uxToHex(metadata.color);
return metadataAction<MetadataUpdateAdd>({
add: {
group: association.group,
resource: {
resource: association.resource,
'app-name': association['app-name']
},
metadata
}
});
};
export { update as metadataUpdate };

View File

@ -1,101 +0,0 @@
import { Path, Patp } from '../lib';
export type MdAppName = 'groups' | 'graph';
export type MetadataUpdate =
MetadataUpdateInitial
| MetadataUpdateAdd
| MetadataUpdateUpdate
| MetadataUpdateRemove
| MetadataUpdateEdit;
export interface MetadataUpdateInitial {
associations: ResourceAssociations;
}
export type ResourceAssociations = {
[p in Path]: Association;
}
export type MetadataUpdateAdd = {
add: AssociationPoke;
}
export type MetadataUpdateUpdate = {
update: AssociationPoke;
}
export interface MetadataUpdateEdit {
edit: {
resource: MdResource;
group: string;
edit: MetadataEditField;
}
}
export type MetadataEditField = Partial<Omit<Metadata, 'config' | 'creator' | 'date-created'>>;
export type MetadataUpdateRemove = {
remove: {
resource: MdResource;
group: string;
}
}
export interface MdResource {
resource: string;
'app-name': MdAppName;
}
export interface MetadataUpdatePreview {
group: string;
channels: Associations;
'channel-count': number;
members: number;
metadata: Metadata;
}
export type Associations = {
groups: AppAssociations<GroupConfig>
graph: AppAssociations<GraphConfig>;
}
export type AppAssociations<C = MetadataConfig> = {
[p in Path]: Association<C>;
}
export type Association<C = MetadataConfig> = MdResource & {
group: Path;
metadata: Metadata<C>;
};
export interface AssociationPoke {
group: Path;
resource: MdResource;
metadata: Metadata;
}
export interface Metadata<C = MetadataConfig> {
color: string;
creator: Patp;
'date-created': string;
description: string;
title: string;
config: C;
hidden: boolean;
picture: string;
preview: boolean;
vip: PermVariation;
}
export type MetadataConfig = GroupConfig | GraphConfig;
export interface GraphConfig {
graph: string;
}
export interface GroupConfig {
group: undefined | {} | MdResource;
}
export type PermVariation = '' | ' ' | 'reader-comments' | 'member-metadata' | 'host-feed' | 'admin-feed';

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
{
"name": "@urbit/api",
"version": "2.2.0",
"description": "A library that provides bindings and types for Urbit's various userspace desks",
"repository": {
"type": "git",
"url": "ssh://git@github.com/urbit/urbit.git",
"directory": "pkg/npm/api"
},
"type": "module",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.js",
"exports": {
"require": "./dist/cjs/index.cjs",
"import": "./dist/esm/index.js"
},
"jsdelivr": "dist/urbit-api.min.js",
"unpkg": "dist/urbit-api.min.js",
"types": "dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"test": "echo \"No test specified\" && exit 0",
"build": "npm run clean && rollup -c && npx tsc -p tsconfig.json",
"prepare": "npm run build",
"watch": "rollup -c -w",
"clean": "rm -rf dist/* types/*"
},
"author": "",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.16.0",
"big-integer": "^1.6.48",
"core-js": "^3.19.1",
"immer": "^9.0.1",
"urbit-ob": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.0.6",
"@types/node": "^15.12.5",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",
"@urbit/eslint-config": "^1.0.3",
"babel-eslint": "^10.1.0",
"eslint-plugin-react": "^7.24.0",
"rollup": "^2.59.0",
"rollup-plugin-analyzer": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^4.3.2"
}
}

View File

@ -1,76 +0,0 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonJS from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import babel from '@rollup/plugin-babel';
import typescript from 'rollup-plugin-typescript2';
import analyze from 'rollup-plugin-analyzer'
const input = ['./index.ts'];
// Skip certain warnings
function onwarn(warning) {
if (warning.code === 'THIS_IS_UNDEFINED') {
return;
}
console.warn(warning.message);
}
export default [
{
input,
onwarn,
plugins: [
nodeResolve({
extensions: ['.js', '.jsx', '.ts', '.tsx']
}),
commonJS(),
typescript(),
babel({
babelHelpers: 'bundled',
exclude: ['node_modules/**']
}),
terser({
ecma: 2017,
compress: true,
mangle: true
})
],
output: {
file: 'dist/urbit-api.min.js',
format: 'umd',
name: 'UrbitAPI', // this is the name of the global object
esModule: false,
exports: 'named',
sourcemap: true
}
},
{
input,
onwarn,
plugins: [
nodeResolve({
extensions: ['.js', '.jsx', '.ts', '.tsx']
}),
commonJS(),
typescript(),
analyze({
limit: 10
})
],
output: [
{
file: 'dist/esm/index.js',
format: 'esm',
exports: 'named',
sourcemap: true,
},
{
file: 'dist/cjs/index.cjs',
format: 'cjs',
exports: 'named',
sourcemap: true
}
]
}
];

View File

@ -1,2 +0,0 @@
export * from './lib';
export * from './types';

View File

@ -1,47 +0,0 @@
import { Poke } from '../lib/types';
import { S3Update, S3UpdateAccessKeyId, S3UpdateAddBucket, S3UpdateCurrentBucket, S3UpdateEndpoint, S3UpdateRemoveBucket, S3UpdateSecretAccessKey } from './types';
const s3Action = <T extends S3Update>(
data: any
): Poke<T> => ({
app: 's3-store',
mark: 's3-action',
json: data
});
export const setCurrentBucket = (
bucket: string
): Poke<S3UpdateCurrentBucket> => s3Action({
'set-current-bucket': bucket
});
export const addBucket = (
bucket: string
): Poke<S3UpdateAddBucket> => s3Action({
'add-bucket': bucket
});
export const removeBucket = (
bucket: string
): Poke<S3UpdateRemoveBucket> => s3Action({
'remove-bucket': bucket
});
export const setEndpoint = (
endpoint: string
): Poke<S3UpdateEndpoint> => s3Action({
'set-endpoint': endpoint
});
export const setAccessKeyId = (
accessKeyId: string
): Poke<S3UpdateAccessKeyId> => s3Action({
'set-access-key-id': accessKeyId
});
export const setSecretAccessKey = (
secretAccessKey: string
): Poke<S3UpdateSecretAccessKey> => s3Action({
'set-secret-access-key': secretAccessKey
});

View File

@ -1,60 +0,0 @@
export interface S3Credentials {
endpoint: string;
accessKeyId: string;
secretAccessKey: string;
}
export interface S3Configuration {
buckets: Set<string>;
currentBucket: string;
}
export interface S3State {
configuration: S3Configuration;
credentials: S3Credentials | null;
}
export interface S3UpdateCredentials {
credentials: S3Credentials;
}
export interface S3UpdateConfiguration {
configuration: {
buckets: string[];
currentBucket: string;
}
}
export interface S3UpdateCurrentBucket {
setCurrentBucket: string;
}
export interface S3UpdateAddBucket {
addBucket: string;
}
export interface S3UpdateRemoveBucket {
removeBucket: string;
}
export interface S3UpdateEndpoint {
setEndpoint: string;
}
export interface S3UpdateAccessKeyId {
setAccessKeyId: string;
}
export interface S3UpdateSecretAccessKey {
setSecretAccessKey: string;
}
export type S3Update =
S3UpdateCredentials
| S3UpdateConfiguration
| S3UpdateCurrentBucket
| S3UpdateAddBucket
| S3UpdateRemoveBucket
| S3UpdateEndpoint
| S3UpdateAccessKeyId
| S3UpdateSecretAccessKey;

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,78 +0,0 @@
import { Poke, Scry } from '../lib';
import { PutBucket, Key, Bucket, DelBucket, Value, PutEntry, DelEntry, SettingsUpdate } from './types';
export const action = <T extends SettingsUpdate>(data: T): Poke<T> => ({
app: 'settings-store',
mark: 'settings-event',
json: data
});
export const putBucket = (
desk: string,
key: Key,
bucket: Bucket
): Poke<PutBucket> => action({
'put-bucket': {
desk,
'bucket-key': key,
'bucket': bucket
}
});
export const delBucket = (
desk: string,
key: Key
): Poke<DelBucket> => action({
'del-bucket': {
desk,
'bucket-key': key
}
});
export const putEntry = (
desk: string,
bucket: Key,
key: Key,
value: Value
): Poke<PutEntry> => action({
'put-entry': {
desk,
'bucket-key': bucket,
'entry-key': key,
value: value
}
});
export const delEntry = (
desk: string,
bucket: Key,
key: Key
): Poke<DelEntry> => action({
'del-entry': {
desk,
'bucket-key': bucket,
'entry-key': key
}
});
export const getAll: Scry = {
app: 'settings-store',
path: '/all'
};
export const getBucket = (desk: string, bucket: string) => ({
app: 'settings-store',
path: `/bucket/${bucket}`
});
export const getEntry = (desk: string, bucket: string, entry: string) => ({
app: 'settings-store',
path: `/entry/${desk}/${bucket}/${entry}`
});
export const getDeskSettings = (desk: string) => ({
app: 'settings-store',
path: `/desk/${desk}`
});
export * from './types';

View File

@ -1,64 +0,0 @@
export type Key = string;
export type Value = string | string[] | boolean | number;
export type Bucket = { [key: string]: Value; };
export type DeskSettings = { [bucket: string]: Bucket; };
export type Settings = { [desk: string]: Settings; }
export interface PutBucket {
'put-bucket': {
desk: string;
'bucket-key': Key;
'bucket': Bucket;
};
}
export interface DelBucket {
'del-bucket': {
desk: string;
'bucket-key': Key;
};
}
export interface PutEntry {
'put-entry': {
'bucket-key': Key;
'entry-key': Key;
'value'?: Value;
};
}
export interface DelEntry {
'del-entry': {
desk: string;
'bucket-key': Key;
'entry-key': Key;
};
}
export interface AllData {
'all': Settings;
}
export interface DeskData {
desk: DeskSettings;
}
export interface BucketData {
'bucket': Bucket;
}
export interface EntryData {
'entry': Value;
}
export type SettingsUpdate =
| PutBucket
| DelBucket
| PutEntry
| DelEntry;
export type SettingsData =
| AllData
| BucketData
| EntryData
| DeskData;

View File

@ -1,2 +0,0 @@
export * from './types';
export * from './lib';

View File

@ -1,20 +0,0 @@
import { Scry } from '../../http-api/src'
import { Poke } from '../../http-api/src/types';
import { Belt, Task, SessionTask } from './types';
export const pokeTask = (session: string, task: Task): Poke<SessionTask> => ({
app: 'herm',
mark: 'herm-task',
json: { session, ...task }
});
export const pokeBelt = (
session: string,
belt: Belt
): Poke<SessionTask> => pokeTask(session, { belt });
//NOTE scry will return string[]
export const scrySessions = (): Scry => ({
app: 'herm',
path: `/sessions`
});

View File

@ -1,61 +0,0 @@
// outputs
//
export type TermUpdate =
| Blit;
export type Tint =
| null
| 'r' | 'g' | 'b' | 'c' | 'm' | 'y' | 'k' | 'w'
| { r: number, g: number, b: number };
export type Deco = null | 'br' | 'un' | 'bl';
export type Stye = {
deco: Deco[],
back: Tint,
fore: Tint
};
export type Stub = {
stye: Stye,
text: string[]
}
export type Blit =
| { bel: null } // make a noise
| { clr: null } // clear the screen
| { hop: number | { x: number, y: number } } // set cursor col/pos
| { klr: Stub[] } // put styled
| { mor: Blit[] } // multiple blits
| { nel: null } // newline
| { put: string[] } // put text at cursor
| { sag: { path: string, file: string } } // save to jamfile
| { sav: { path: string, file: string } } // save to file
| { url: string } // activate url
| { wyp: null } // wipe cursor line
// inputs
//
export type Bolt =
| string
| { aro: 'd' | 'l' | 'r' | 'u' }
| { bac: null }
| { del: null }
| { hit: { x: number, y: number } }
| { ret: null }
export type Belt =
| Bolt
| { mod: { mod: 'ctl' | 'met' | 'hyp', key: Bolt } }
| { txt: Array<string> }
export type Task =
| { belt: Belt }
| { blew: { w: number, h: number } }
| { hail: null }
| { open: { term: string, apps: Array<{ who: string, app: string }> } }
| { shut: null }
export type SessionTask = { session: string } & Task

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "./tmp",
"module": "ESNext",
"noImplicitAny": true,
"target": "ESNext",
"pretty": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"declaration": true,
"sourceMap": true,
"strict": false,
"noErrorTruncation": true
},
"exclude": [
"node_modules",
"./dist/**/*",
"./tmp/**/*",
"rollup.config.ts"
],
"include": ["./*.ts"]
}

View File

@ -1,204 +0,0 @@
const env = {
"browser": true,
"es6": true,
"node": true
};
const rules = {
"array-bracket-spacing": ["error", "never"],
"arrow-parens": [
"error",
"as-needed",
{
"requireForBlockBody": true
}
],
"arrow-spacing": "error",
"@typescript-eslint/ban-ts-comment": [2,
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': 'allow-with-description',
'ts-check': 'allow-with-description',
minimumDescriptionLength: 3,
}],
"@typescript-eslint/ban-types": "off",
"block-spacing": ["error", "always"],
"brace-style": ["error", "1tbs"],
"camelcase": [
"error",
{
"properties": "never"
}
],
"comma-dangle": ["error", "never"],
"eol-last": ["error", "always"],
"@typescript-eslint/explicit-module-boundary-types": "off",
"func-name-matching": "error",
"indent": [
"off",
2,
{
"ArrayExpression": "off",
"SwitchCase": 1,
"CallExpression": {
"arguments": "off"
},
"FunctionDeclaration": {
"parameters": "off"
},
"FunctionExpression": {
"parameters": "off"
},
"MemberExpression": "off",
"ObjectExpression": "off",
"ImportDeclaration": "off"
}
],
"handle-callback-err": "off",
"linebreak-style": ["error", "unix"],
"max-lines": [
"warn",
{
"max": 300,
"skipBlankLines": true,
"skipComments": true
}
],
"max-lines-per-function": [
"warn",
{
"skipBlankLines": true,
"skipComments": true
}
],
"max-statements-per-line": [
"error",
{
"max": 1
}
],
"new-cap": [
"error",
{
"newIsCap": true,
"capIsNew": false
}
],
"new-parens": "error",
"no-buffer-constructor": "error",
"no-console": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"no-extra-semi": "off",
"no-fallthrough": "off",
"no-func-assign": "off",
"no-implicit-coercion": "error",
"no-multi-assign": "error",
"no-multiple-empty-lines": [
"error",
{
"max": 1
}
],
"no-nested-ternary": "warn",
"no-param-reassign": "off",
"no-return-assign": "error",
"no-return-await": "off",
"no-shadow-restricted-names": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"vars": "all",
"args": "none",
"ignoreRestSiblings": false
}
],
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": [
"error",
{
"functions": false,
"classes": false
}
],
"no-useless-escape": "off",
"no-var": "error",
"nonblock-statement-body-position": ["error", "below"],
"object-curly-spacing": ["error", "always"],
"padded-blocks": ["error", "never"],
"prefer-arrow-callback": "error",
"prefer-const": [
"error",
{
"destructuring": "all",
"ignoreReadBeforeAssign": true
}
],
"prefer-template": "off",
"quotes": ["error", "single"],
"react/display-name": "off",
"semi": ["error", "always"],
"spaced-comment": [
"error",
"always",
{
"exceptions": ["!"]
}
],
"space-before-blocks": "error",
"unicode-bom": ["error", "never"],
"valid-jsdoc": "error",
"wrap-iife": ["error", "inside"],
"react/jsx-closing-bracket-location": 1,
"react/jsx-tag-spacing": 1,
"react/jsx-max-props-per-line": ["error", { "maximum": 2, "when": "multiline" }],
"react/prop-types": 0
};
module.exports = {
"env": env,
"extends": [
"plugin:react/recommended",
"eslint:recommended",
],
"settings": {
"react": {
"version": "^16.5.2"
}
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 10,
"requireConfigFile": false,
"sourceType": "module"
},
"root": true,
"rules": rules,
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"env": env,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": { "jsx": true },
"ecmaVersion": 10,
"requireConfigFile": false,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
...rules,
"valid-jsdoc": "off"
}
}
]
};

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
{
"name": "@urbit/eslint-config",
"version": "1.0.3",
"description": "",
"repository": {
"type": "git",
"url": "ssh://git@github.com/urbit/urbit.git",
"directory": "pkg/npm/eslint-config"
},
"main": "index.js",
"scripts": {},
"author": "",
"license": "MIT",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.15.0",
"@typescript-eslint/parser": "^4.15.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.26.0",
"eslint-plugin-react": "^7.22.0",
"typescript": "^4.1.5"
}
}

View File

@ -1,13 +0,0 @@
{
"presets": [
"@babel/preset-typescript", //needed for .ts jest tests
[
"@babel/preset-env",
{
"targets": "> 1%",
"useBuiltIns": "usage",
"corejs": "3.19.1"
}
]
]
}

View File

@ -1,4 +0,0 @@
dist
node_modules
coverage
tmp

View File

@ -1,21 +0,0 @@
# Urbit Connector
This project allows you to connect to an [Urbit](https://urbit.org) ship via a JavaScript application.
## Example
Check out the `example` directory for examples of how to use this code.
1. Open `example/index.html` in your browser and follow the instructions there, or
2. With a ship running in the same fashion as indicated in the file above, run `node example/index.js`
The code for either of these can be found in `src/example/browser.js` or `src/example/node.js`, depending on your context.
## Design
This library is designed to be useful for node applications that communicate with an urbit running either on the local computer or on a remote one.
The majority of its methods are asynchronous and return Promises. This is due to the non-blocking nature of JavaScript. If used in a React app, response handlers should be bound with `this` to `setState` after a message is received.
## NOTE
You must enable CORS requests on your urbit for this library to work in browser context. Use `+cors-registry` to see domains which have made requests to your urbit, and then approve the needed one, e.g. `|cors-approve http://zod.arvo.network`.

View File

@ -1,3 +0,0 @@
// import Urbit from '../../dist/browser';
// window.Urbit = Urbit;

View File

@ -1,122 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Demo</title>
<script src="../dist/urbit-http-api.min.js"></script>
<style>
@import url('https://rsms.me/inter/inter.css');
@font-face {
font-family: 'Source Code Pro';
src: url('https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff');
font-weight: 400;
}
body {
margin: 0 auto;
max-width: 70ch;
padding: 2ch;
font-family: 'Inter', sans-serif;
}
#mylog {
white-space: pre-wrap;
padding: 2ch;
background: black;
color: white;
font-family: 'Source Code Pro', monospace;
}
#mylog div {
margin-bottom: 1rem;
}
.chunk {
border-bottom: 1px dashed currentColor;
}
</style>
</head>
<body>
<details>
<summary>Show instructions</summary>
<p>Assuming you are running a fakezod on port 8080, run</p>
<code id="instructions">|cors-approve '{window.location.origin}'</code>
<p>in its dojo.</p>
<p>
Press the button to run the code below. Output will be logged. You
should see <code>&lt; ~zod: opening airlock</code> in your dojo. Create
a chat and send a message to see the events logged.
</p>
<pre>
window.airlock = await UrbitHttpApi.Urbit.authenticate({
ship: 'zod',
url: 'localhost:8080',
code: 'lidlut-tabwed-pillex-ridrup',
verbose: true
});
window.airlock.subscribe({
app: 'graph-store',
path: '/updates',
event: console.log
});</pre
>
</details>
<button id="blastoff" onclick="blastOff()">Blast Off</button>
<pre id="mylog"></pre>
</body>
<script>
var baseLogFunction = console.log;
console.log = function () {
baseLogFunction.apply(console, arguments);
var chunk = document.createElement('div');
chunk.className = 'chunk';
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < args.length; i++) {
const val =
typeof args[i] === 'string' ? args[i] : JSON.stringify(args[i]);
var node = createLogNode(val);
chunk.appendChild(node);
}
document
.querySelector('#mylog')
.insertBefore(chunk, document.querySelector('#mylog').firstChild);
};
function createLogNode(message) {
var node = document.createElement('div');
node.className = 'message';
var textNode = document.createTextNode(message);
node.appendChild(textNode);
return node;
}
window.onerror = function (message, url, linenumber) {
console.log(
'JavaScript error: ' +
message +
' on line ' +
linenumber +
' for ' +
url
);
};
const instructions = document.getElementById('instructions');
instructions.innerText = instructions.innerText.replace(
'{window.location.origin}',
window.location.origin
);
async function blastOff() {
window.airlock = await UrbitHttpApi.Urbit.authenticate({
ship: 'zod',
url: 'localhost',
code: 'lidlut-tabwed-pillex-ridrup',
verbose: true,
});
window.airlock.subscribe({
app: 'graph-store',
path: '/updates',
event: console.log,
});
document.body.removeChild(document.getElementById('blastoff'));
}
</script>
</html>

View File

@ -1,14 +0,0 @@
// import Urbit from '../../dist/index';
// async function blastOff() {
// const airlock = await Urbit.authenticate({
// ship: 'zod',
// url: 'localhost:8080',
// code: 'lidlut-tabwed-pillex-ridrup',
// verbose: true
// });
// airlock.subscribe('chat-view', '/primary');
// }
// blastOff();

View File

@ -1,194 +0,0 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
module.exports = {
// All imported modules in your tests should be mocked automatically
automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/7w/hvrpvq7978bbb9kwkbhsn6rr0000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
maxWorkers: 1,
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: ['./setupEnv.js'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
testURL: 'http://localhost',
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,70 +0,0 @@
{
"name": "@urbit/http-api",
"version": "2.3.0",
"license": "MIT",
"description": "Library to interact with an Urbit ship over HTTP",
"repository": {
"type": "git",
"url": "ssh://git@github.com/urbit/urbit.git",
"directory": "pkg/npm/http-api"
},
"type": "module",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.js",
"exports": {
"require": "./dist/cjs/index.cjs",
"import": "./dist/esm/index.js"
},
"jsdelivr": "dist/urbit-http-api.min.js",
"unpkg": "dist/urbit-http-api.min.js",
"types": "dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"test": "jest",
"build": "npm run clean && rollup -c && npx tsc -p tsconfig.json",
"prepare": "npm run build",
"watch": "rollup -c -w",
"clean": "rm -rf dist/* types/*"
},
"prettier": {
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
},
"author": "",
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"@babel/preset-typescript": "^7.16.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.0.6",
"@types/browser-or-node": "^1.2.0",
"@types/eventsource": "^1.1.5",
"@types/jest": "^26.0.24",
"@types/react": "^16.9.56",
"@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0",
"babel-jest": "^27.0.6",
"cross-fetch": "^3.1.4",
"event-target-polyfill": "0.0.3",
"fast-text-encoding": "^1.0.3",
"jest": "^27.0.6",
"rollup": "^2.59.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^3.9.7",
"util": "^0.12.3",
"web-streams-polyfill": "^3.0.3",
"yet-another-abortcontroller-polyfill": "0.0.4"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@microsoft/fetch-event-source": "^2.0.0",
"browser-or-node": "^1.3.0",
"core-js": "^3.19.1"
}
}

View File

@ -1,72 +0,0 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonJS from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import babel from '@rollup/plugin-babel';
import typescript from 'rollup-plugin-typescript2';
const input = ['src/index.ts'];
// Skip certain warnings
function onwarn(warning) {
if (warning.code === 'THIS_IS_UNDEFINED') {
return;
}
console.warn(warning.message);
}
export default [
{
input,
onwarn,
plugins: [
nodeResolve({
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
commonJS(),
typescript(),
babel({
babelHelpers: 'bundled',
exclude: ['node_modules/**'],
}),
terser({
ecma: 2017,
compress: true,
mangle: true,
}),
],
output: {
file: `dist/urbit-http-api.min.js`,
format: 'umd',
name: 'UrbitHttpApi', // this is the name of the global object
esModule: false,
exports: 'named',
sourcemap: true,
},
},
{
input,
onwarn,
plugins: [
nodeResolve({
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
commonJS(),
typescript(),
],
output: [
{
file: 'dist/esm/index.js',
format: 'esm',
exports: 'named',
sourcemap: true,
},
{
file: 'dist/cjs/index.cjs',
format: 'cjs',
exports: 'named',
sourcemap: true,
},
],
},
];

View File

@ -1,8 +0,0 @@
require('event-target-polyfill');
require('yet-another-abortcontroller-polyfill');
require('cross-fetch/polyfill');
require('fast-text-encoding');
require('web-streams-polyfill');
global.ReadableStream = require('web-streams-polyfill').ReadableStream;

View File

@ -1,602 +0,0 @@
import { isBrowser, isNode } from 'browser-or-node';
import {
fetchEventSource,
EventSourceMessage,
} from '@microsoft/fetch-event-source';
import {
Scry,
Thread,
AuthenticationInterface,
PokeInterface,
SubscriptionRequestInterface,
headers,
SSEOptions,
PokeHandlers,
Message,
FatalError,
} from './types';
import { hexString } from './utils';
/**
* A class for interacting with an urbit ship, given its URL and code
*/
export class Urbit {
/**
* UID will be used for the channel: The current unix time plus a random hex string
*/
private uid: string = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`;
/**
* lastEventId is an auto-updated index of which events have been *sent* over this channel.
* lastHeardEventId is the latest event we have heard back about.
* lastAcknowledgedEventId is the latest event we have sent an ack for.
*/
private lastEventId: number = 0;
private lastHeardEventId: number = -1;
private lastAcknowledgedEventId: number = -1;
/**
* SSE Client is null for now; we don't want to start polling until it the channel exists
*/
private sseClientInitialized: boolean = false;
/**
* Cookie gets set when we log in.
*/
cookie?: string | undefined;
/**
* A registry of requestId to successFunc/failureFunc
*
* These functions are registered during a +poke and are executed
* in the onServerEvent()/onServerError() callbacks. Only one of
* the functions will be called, and the outstanding poke will be
* removed after calling the success or failure function.
*/
private outstandingPokes: Map<number, PokeHandlers> = new Map();
/**
* A registry of requestId to subscription functions.
*
* These functions are registered during a +subscribe and are
* executed in the onServerEvent()/onServerError() callbacks. The
* event function will be called whenever a new piece of data on this
* subscription is available, which may be 0, 1, or many times. The
* disconnect function may be called exactly once.
*/
private outstandingSubscriptions: Map<number, SubscriptionRequestInterface> =
new Map();
/**
* Our abort controller, used to close the connection
*/
private abort = new AbortController();
/**
* Ship can be set, in which case we can do some magic stuff like send chats
*/
ship?: string | null;
/**
* If verbose, logs output eagerly.
*/
verbose?: boolean;
/**
* number of consecutive errors in connecting to the eventsource
*/
private errorCount = 0;
onError?: (error: any) => void = null;
onRetry?: () => void = null;
onOpen?: () => void = null;
/** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */
private get channelUrl(): string {
return `${this.url}/~/channel/${this.uid}`;
}
private get fetchOptions(): any {
const headers: headers = {
'Content-Type': 'application/json',
};
if (!isBrowser) {
headers.Cookie = this.cookie;
}
return {
credentials: 'include',
accept: '*',
headers,
signal: this.abort.signal,
};
}
/**
* Constructs a new Urbit connection.
*
* @param url The URL (with protocol and port) of the ship to be accessed. If
* the airlock is running in a webpage served by the ship, this should just
* be the empty string.
* @param code The access code for the ship at that address
*/
constructor(public url: string, public code?: string, public desk?: string) {
if (isBrowser) {
window.addEventListener('beforeunload', this.delete);
}
return this;
}
/**
* All-in-one hook-me-up.
*
* Given a ship, url, and code, this returns an airlock connection
* that is ready to go. It `|hi`s itself to create the channel,
* then opens the channel via EventSource.
*
*/
static async authenticate({
ship,
url,
code,
verbose = false,
}: AuthenticationInterface) {
const airlock = new Urbit(url.startsWith('http') ? url : `http://${url}`, code);
airlock.verbose = verbose;
airlock.ship = ship;
await airlock.connect();
await airlock.poke({
app: 'hood',
mark: 'helm-hi',
json: 'opening airlock',
});
await airlock.eventSource();
return airlock;
}
/**
* Connects to the Urbit ship. Nothing can be done until this is called.
* That's why we roll it into this.authenticate
*/
async connect(): Promise<void> {
if (this.verbose) {
console.log(
`password=${this.code} `,
isBrowser
? 'Connecting in browser context at ' + `${this.url}/~/login`
: 'Connecting from node context'
);
}
return fetch(`${this.url}/~/login`, {
method: 'post',
body: `password=${this.code}`,
credentials: 'include',
}).then((response) => {
if (this.verbose) {
console.log('Received authentication response', response);
}
const cookie = response.headers.get('set-cookie');
if (!this.ship) {
this.ship = new RegExp(/urbauth-~([\w-]+)/).exec(cookie)[1];
}
if (!isBrowser) {
this.cookie = cookie;
}
});
}
/**
* Initializes the SSE pipe for the appropriate channel.
*/
async eventSource(): Promise<void> {
if (this.sseClientInitialized) {
return Promise.resolve();
}
if (this.lastEventId === 0) {
// Can't receive events until the channel is open,
// so poke and open then
await this.poke({
app: 'hood',
mark: 'helm-hi',
json: 'Opening API channel',
});
return;
}
this.sseClientInitialized = true;
return new Promise((resolve, reject) => {
const sseOptions: SSEOptions = {
headers: {},
};
if (isBrowser) {
sseOptions.withCredentials = true;
} else if (isNode) {
sseOptions.headers.Cookie = this.cookie;
}
fetchEventSource(this.channelUrl, {
...this.fetchOptions,
openWhenHidden: true,
onopen: async (response) => {
if (this.verbose) {
console.log('Opened eventsource', response);
}
if (response.ok) {
this.errorCount = 0;
this.onOpen && this.onOpen();
resolve();
return; // everything's good
} else {
const err = new Error('failed to open eventsource');
reject(err);
}
},
onmessage: (event: EventSourceMessage) => {
if (this.verbose) {
console.log('Received SSE: ', event);
}
if (!event.id) return;
const eventId = parseInt(event.id, 10);
if (eventId <= this.lastHeardEventId) {
console.log('dropping old or out-of-order event', {
eventId, lastHeard: this.lastHeardEventId
});
return;
}
this.lastHeardEventId = eventId;
if (eventId - this.lastAcknowledgedEventId > 20) {
this.ack(eventId);
}
if (event.data && JSON.parse(event.data)) {
const data: any = JSON.parse(event.data);
if (
data.response === 'poke' &&
this.outstandingPokes.has(data.id)
) {
const funcs = this.outstandingPokes.get(data.id);
if (data.hasOwnProperty('ok')) {
funcs.onSuccess();
} else if (data.hasOwnProperty('err')) {
console.error(data.err);
funcs.onError(data.err);
} else {
console.error('Invalid poke response', data);
}
this.outstandingPokes.delete(data.id);
} else if (
data.response === 'subscribe' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id);
if (data.hasOwnProperty('err')) {
console.error(data.err);
funcs.err(data.err, data.id);
this.outstandingSubscriptions.delete(data.id);
}
} else if (
data.response === 'diff' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id);
try {
funcs.event(data.json, data.mark ?? 'json');
} catch (e) {
console.error('Failed to call subscription event callback', e);
}
} else if (
data.response === 'quit' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id);
funcs.quit(data);
this.outstandingSubscriptions.delete(data.id);
} else {
console.log([...this.outstandingSubscriptions.keys()]);
console.log('Unrecognized response', data);
}
}
},
onerror: (error) => {
console.warn(error);
if (!(error instanceof FatalError) && this.errorCount++ < 4) {
this.onRetry && this.onRetry();
return Math.pow(2, this.errorCount - 1) * 750;
}
this.onError && this.onError(error);
throw error;
},
onclose: () => {
console.log('e');
throw new Error('Ship unexpectedly closed the connection');
},
});
});
}
/**
* Reset airlock, abandoning current subscriptions and wiping state
*
*/
reset() {
if (this.verbose) {
console.log('resetting');
}
this.delete();
this.abort.abort();
this.abort = new AbortController();
this.uid = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`;
this.lastEventId = 0;
this.lastHeardEventId = -1;
this.lastAcknowledgedEventId = -1;
this.outstandingSubscriptions = new Map();
this.outstandingPokes = new Map();
this.sseClientInitialized = false;
}
/**
* Autoincrements the next event ID for the appropriate channel.
*/
private getEventId(): number {
return ++this.lastEventId;
}
/**
* Acknowledges an event.
*
* @param eventId The event to acknowledge.
*/
private async ack(eventId: number): Promise<number | void> {
this.lastAcknowledgedEventId = eventId;
const message: Message = {
action: 'ack',
'event-id': eventId,
};
await this.sendJSONtoChannel(message);
return eventId;
}
private async sendJSONtoChannel(...json: Message[]): Promise<void> {
const response = await fetch(this.channelUrl, {
...this.fetchOptions,
method: 'PUT',
body: JSON.stringify(json),
});
if (!response.ok) {
throw new Error('Failed to PUT channel');
}
if (!this.sseClientInitialized) {
await this.eventSource();
}
}
/**
* Creates a subscription, waits for a fact and then unsubscribes
*
* @param app Name of gall agent to subscribe to
* @param path Path to subscribe to
* @param timeout Optional timeout before ending subscription
*
* @returns The first fact on the subcription
*/
async subscribeOnce<T = any>(app: string, path: string, timeout?: number) {
return new Promise<T>(async (resolve, reject) => {
let done = false;
let id: number | null = null;
const quit = () => {
if (!done) {
reject('quit');
}
};
const event = (e: T) => {
if (!done) {
resolve(e);
this.unsubscribe(id);
}
};
const request = { app, path, event, err: reject, quit };
id = await this.subscribe(request);
if (timeout) {
setTimeout(() => {
if (!done) {
done = true;
reject('timeout');
this.unsubscribe(id);
}
}, timeout);
}
});
}
/**
* Pokes a ship with data.
*
* @param app The app to poke
* @param mark The mark of the data being sent
* @param json The data to send
*/
async poke<T>(params: PokeInterface<T>): Promise<number> {
const { app, mark, json, ship, onSuccess, onError } = {
onSuccess: () => {},
onError: () => {},
ship: this.ship,
...params,
};
const message: Message = {
id: this.getEventId(),
action: 'poke',
ship,
app,
mark,
json,
};
const [send, result] = await Promise.all([
this.sendJSONtoChannel(message),
new Promise<number>((resolve, reject) => {
this.outstandingPokes.set(message.id, {
onSuccess: () => {
onSuccess();
resolve(message.id);
},
onError: (event) => {
onError(event);
reject(event.err);
},
});
}),
]);
return result;
}
/**
* Subscribes to a path on an app on a ship.
*
*
* @param app The app to subsribe to
* @param path The path to which to subscribe
* @param handlers Handlers to deal with various events of the subscription
*/
async subscribe(params: SubscriptionRequestInterface): Promise<number> {
const { app, path, ship, err, event, quit } = {
err: () => {},
event: () => {},
quit: () => {},
ship: this.ship,
...params,
};
const message: Message = {
id: this.getEventId(),
action: 'subscribe',
ship,
app,
path,
};
this.outstandingSubscriptions.set(message.id, {
app,
path,
err,
event,
quit,
});
await this.sendJSONtoChannel(message);
return message.id;
}
/**
* Unsubscribes to a given subscription.
*
* @param subscription
*/
async unsubscribe(subscription: number) {
return this.sendJSONtoChannel({
id: this.getEventId(),
action: 'unsubscribe',
subscription,
}).then(() => {
this.outstandingSubscriptions.delete(subscription);
});
}
/**
* Deletes the connection to a channel.
*/
delete() {
if (isBrowser) {
navigator.sendBeacon(
this.channelUrl,
JSON.stringify([
{
action: 'delete',
},
])
);
} else {
// TODO
// this.sendMessage('delete');
}
}
/**
* Scry into an gall agent at a path
*
* @typeParam T - Type of the scry result
*
* @remarks
*
* Equivalent to
* ```hoon
* .^(T %gx /(scot %p our)/[app]/(scot %da now)/[path]/json)
* ```
* The returned cage must have a conversion to JSON for the scry to succeed
*
* @param params The scry request
* @returns The scry result
*/
async scry<T = any>(params: Scry): Promise<T> {
const { app, path } = params;
const response = await fetch(
`${this.url}/~/scry/${app}${path}.json`,
this.fetchOptions
)
if (!response.ok) {
return Promise.reject(response);
}
return await response.json();
}
/**
* Run a thread
*
*
* @param inputMark The mark of the data being sent
* @param outputMark The mark of the data being returned
* @param threadName The thread to run
* @param body The data to send to the thread
* @returns The return value of the thread
*/
async thread<R, T = any>(params: Thread<T>): Promise<R> {
const {
inputMark,
outputMark,
threadName,
body,
desk = this.desk,
} = params;
if (!desk) {
throw new Error('Must supply desk to run thread from');
}
const res = await fetch(
`${this.url}/spider/${desk}/${inputMark}/${threadName}/${outputMark}.json`,
{
...this.fetchOptions,
method: 'POST',
body: JSON.stringify(body),
}
);
return res.json();
}
/**
* Utility function to connect to a ship that has its *.arvo.network domain configured.
*
* @param name Name of the ship e.g. zod
* @param code Code to log in
*/
static async onArvoNetwork(ship: string, code: string): Promise<Urbit> {
const url = `https://${ship}.arvo.network`;
return await Urbit.authenticate({ ship, url, code });
}
}
export default Urbit;

View File

@ -1,3 +0,0 @@
export * from './types';
import { Urbit } from './Urbit';
export { Urbit as default, Urbit };

View File

@ -1,198 +0,0 @@
/**
* An urbit style path, rendered as a Javascript string
* @example
* `"/updates"`
*/
export type Path = string;
/**
* @p including leading sig, rendered as a string
*
* @example
* ```typescript
* "~sampel-palnet"
* ```
*
*/
export type Patp = string;
/**
* @p not including leading sig, rendered as a string
*
* @example
* ```typescript
* "sampel-palnet"
* ```
*
*/
export type PatpNoSig = string;
/**
* The name of a clay mark, as a string
*
* @example
* ```typescript
* "graph-update"
* ```
*/
export type Mark = string;
/**
* The name of a gall agent, as a string
*
* @example
*
* ```typescript
* "graph-store"
* ```
*/
export type GallAgent = string;
/**
* Description of an outgoing poke
*
* @typeParam Action - Typescript type of the data being poked
*/
export interface Poke<Action> {
/**
* Ship to poke. If left empty, the api lib will populate it with the ship that it is connected to.
*
* @remarks
*
* This should always be the ship that you are connected to
*
*/
ship?: PatpNoSig;
/**
*/
app: GallAgent;
/**
* Mark of the cage to be poked
*
*/
mark: Mark;
/**
* Vase of the cage of to be poked, as JSON
*/
json: Action;
}
/**
* Description of a scry request
*/
export interface Scry {
/** {@inheritDoc GallAgent} */
app: GallAgent;
/** {@inheritDoc Path} */
path: Path;
}
/**
* Description of a thread request
*
* @typeParam Action - Typescript type of the data being poked
*/
export interface Thread<Action> {
/**
* The mark of the input vase
*/
inputMark: Mark;
/**
* The mark of the output vase
*/
outputMark: Mark;
/**
* Name of the thread
*
* @example
* ```typescript
* "graph-add-nodes"
* ```
*/
threadName: string;
/**
* Desk of thread
*/
desk?: string;
/**
* Data of the input vase
*/
body: Action;
}
export type Action = 'poke' | 'subscribe' | 'ack' | 'unsubscribe' | 'delete';
export interface PokeHandlers {
onSuccess?: () => void;
onError?: (e: any) => void;
}
export type PokeInterface<T> = PokeHandlers & Poke<T>;
export interface AuthenticationInterface {
ship: string;
url: string;
code: string;
verbose?: boolean;
}
/**
* Subscription event handlers
*
*/
export interface SubscriptionInterface {
/**
* Handle negative %watch-ack
*/
err?(error: any, id: string): void;
/**
* Handle %fact
*/
event?(data: any, mark: string): void;
/**
* Handle %kick
*/
quit?(data: any): void;
}
export type OnceSubscriptionErr = 'quit' | 'nack' | 'timeout';
export interface SubscriptionRequestInterface extends SubscriptionInterface {
/**
* The app to subscribe to
* @example
* `"graph-store"`
*/
app: GallAgent;
/**
* The path to which to subscribe
* @example
* `"/keys"`
*/
path: Path;
}
export interface headers {
'Content-Type': string;
Cookie?: string;
}
export interface CustomEventHandler {
(data: any, response: string): void;
}
export interface SSEOptions {
headers?: {
Cookie?: string;
};
withCredentials?: boolean;
}
export interface Message extends Record<string, any> {
action: Action;
id?: number;
}
export class ResumableError extends Error {}
export class FatalError extends Error {}

View File

@ -1,53 +0,0 @@
export function camelize(str: string) {
return str
.replace(/\s(.)/g, function ($1: string) {
return $1.toUpperCase();
})
.replace(/\s/g, '')
.replace(/^(.)/, function ($1: string) {
return $1.toLowerCase();
});
}
export function uncamelize(str: string, separator = '-') {
// Replace all capital letters by separator followed by lowercase one
var str = str.replace(/[A-Z]/g, function (letter: string) {
return separator + letter.toLowerCase();
});
return str.replace(new RegExp('^' + separator), '');
}
/**
* Returns a hex string of given length.
*
* Poached from StackOverflow.
*
* @param len Length of hex string to return.
*/
export function hexString(len: number): string {
const maxlen = 8;
const min = Math.pow(16, Math.min(len, maxlen) - 1);
const max = Math.pow(16, Math.min(len, maxlen)) - 1;
const n = Math.floor(Math.random() * (max - min + 1)) + min;
let r = n.toString(16);
while (r.length < len) {
r = r + hexString(len - maxlen);
}
return r;
}
/**
* Generates a random UID.
*
* Copied from https://github.com/urbit/urbit/blob/137e4428f617c13f28ed31e520eff98d251ed3e9/pkg/interface/src/lib/util.js#L3
*/
export function uid(): string {
let str = '0v';
str += Math.ceil(Math.random() * 8) + '.';
for (let i = 0; i < 5; i++) {
let _str = Math.ceil(Math.random() * 10000000).toString(32);
_str = ('00000' + _str).substr(-5, 5);
str += _str + '.';
}
return str.slice(0, -1);
}

View File

@ -1,180 +0,0 @@
import Urbit from '../src';
import 'jest';
function fakeSSE(messages = [], timeout = 0) {
const ourMessages = [...messages];
const enc = new TextEncoder();
return new ReadableStream({
start(controller) {
const interval = setInterval(() => {
let message = ':\n';
if (ourMessages.length > 0) {
message = ourMessages.shift();
}
controller.enqueue(enc.encode(message));
}, 50);
if (timeout > 0) {
setTimeout(() => {
controller.close();
clearInterval(interval);
interval;
}, timeout);
}
},
});
}
const ship = '~sampel-palnet';
let eventId = 0;
function event(data: any) {
return `id:${eventId++}\ndata:${JSON.stringify(data)}\n\n`;
}
function fact(id: number, data: any) {
return event({
response: 'diff',
id,
json: data,
});
}
function ack(id: number, err = false) {
const res = err ? { err: 'Error' } : { ok: true };
return event({ id, response: 'poke', ...res });
}
const fakeFetch = (body) => () =>
Promise.resolve({
ok: true,
body: body(),
});
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
process.on('unhandledRejection', (error) => {
console.error(error);
});
describe('Initialisation', () => {
let airlock: Urbit;
let fetchSpy;
beforeEach(() => {
airlock = new Urbit('', '+code');
});
afterEach(() => {
fetchSpy.mockReset();
});
it('should poke & connect upon a 200', async () => {
airlock.onOpen = jest.fn();
fetchSpy = jest.spyOn(window, 'fetch');
fetchSpy
.mockImplementationOnce(() =>
Promise.resolve({ ok: true, body: fakeSSE() })
)
.mockImplementationOnce(() =>
Promise.resolve({ ok: true, body: fakeSSE([ack(1)]) })
);
await airlock.eventSource();
expect(airlock.onOpen).toHaveBeenCalled();
}, 500);
it('should handle failures', async () => {
fetchSpy = jest.spyOn(window, 'fetch');
airlock.onRetry = jest.fn();
airlock.onOpen = jest.fn();
fetchSpy
.mockImplementationOnce(() =>
Promise.resolve({ ok: true, body: fakeSSE() })
)
.mockImplementationOnce(() =>
Promise.resolve({ ok: true, body: fakeSSE([], 100) })
);
airlock.onError = jest.fn();
try {
airlock.eventSource();
await wait(200);
} catch (e) {
expect(airlock.onRetry).toHaveBeenCalled();
}
}, 300);
});
describe('subscription', () => {
let airlock: Urbit;
let fetchSpy: jest.SpyInstance;
beforeEach(() => {
eventId = 1;
});
afterEach(() => {
fetchSpy.mockReset();
});
it('should subscribe', async () => {
fetchSpy = jest.spyOn(window, 'fetch');
airlock = new Urbit('', '+code');
airlock.onOpen = jest.fn();
const params = {
app: 'app',
path: '/path',
err: jest.fn(),
event: jest.fn(),
quit: jest.fn(),
};
const firstEv = 'one';
const secondEv = 'two';
const events = (id) => [fact(id, firstEv), fact(id, secondEv)];
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE(events(1))));
await airlock.subscribe(params);
await wait(600);
expect(airlock.onOpen).toBeCalled();
expect(params.event).toHaveBeenNthCalledWith(1, firstEv, 'json');
expect(params.event).toHaveBeenNthCalledWith(2, secondEv, 'json');
}, 800);
it('should poke', async () => {
fetchSpy = jest.spyOn(window, 'fetch');
airlock = new Urbit('', '+code');
airlock.onOpen = jest.fn();
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE([ack(1)])));
const params = {
app: 'app',
mark: 'mark',
json: { poke: 1 },
onSuccess: jest.fn(),
onError: jest.fn(),
};
await airlock.poke(params);
await wait(300);
expect(params.onSuccess).toHaveBeenCalled();
}, 800);
it('should nack poke', async () => {
fetchSpy = jest.spyOn(window, 'fetch');
airlock = new Urbit('', '+code');
airlock.onOpen = jest.fn();
fetchSpy
.mockImplementationOnce(() =>
Promise.resolve({ ok: true, body: fakeSSE() })
)
.mockImplementationOnce(() =>
Promise.resolve({ ok: false, body: fakeSSE([ack(1, true)]) })
);
const params = {
app: 'app',
mark: 'mark',
json: { poke: 1 },
onSuccess: jest.fn(),
onError: jest.fn(),
};
try {
await airlock.poke(params);
await wait(300);
} catch (e) {
expect(true).toBe(true);
}
});
});

View File

@ -1,24 +0,0 @@
{
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "tmp"],
"compilerOptions": {
"outDir": "./tmp",
"module": "ESNext",
"target": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"declaration": true,
"sourceMap": true,
"strict": false,
"pretty": true,
"noImplicitAny": true,
"noErrorTruncation": true,
"allowJs": true,
"baseUrl": ".",
"paths": {
"*": ["./node_modules/@types/*", "*"]
}
}
}

View File

@ -2,10 +2,7 @@
set -ex
cd pkg/npm/api
npm install &
cd ../../interface
cd pkg/interface
npm install
npm run build:prod &