Merge branch 'next/groups' into hm/remove-dm-delete

This commit is contained in:
Hunter Miller 2022-01-11 18:16:29 -06:00
commit 846502ef51
93 changed files with 85318 additions and 75375 deletions

View File

@ -168,6 +168,11 @@
%+ turn ~(tap by charges) %+ turn ~(tap by charges)
|= [=desk =charge] |= [=desk =charge]
[desk (get-light-charge charge)] [desk (get-light-charge charge)]
::
[%x %charges @ %version ~]
?~ charge=(~(get by charges) i.t.t.path)
[~ ~]
``noun+!>(version.docket.u.charge)
== ==
:: ::
++ on-agent ++ on-agent

View File

@ -4,4 +4,5 @@ bin/
.vscode/ .vscode/
.husky/ .husky/
*.config.js *.config.js
*.config.ts *.config.ts
package.json

47
pkg/grid/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,47 @@
# Contributing to Landscape
Thank you for your interest in contributing to the Urbit ecosystem.
Landscape is entirely open to contributions from the community. We mainly organize through our [project board], [issues], and [weekly call].
For now our code is stored in the main [urbit repo]. If you would like to contribute feel free to open up a PR there.
## Git Conventions
For Landscape we follow the same conventions as the main repo which can be found in it's [contributing doc]. This can be summarized as the following:
Commits should try to be atomic and focused on one feature at a time. Work-in-progress commits should be rebased and combined into one so that the commit history stays clean.
Commits should follow this format:
> component: short description
>
> long description
Where `component` is the closest most relevant area of the code base written as concisely as possible. The short description that accompanies should be a super concise summary of the changes. The total length of the commit message should strive to be 50 characters or less. The long description is optional, but should be used if further explanation is necessary.
### Pull Requests
A pull request (PR) should have a title similar in structure to our commit messages where it has a short identifier component followed by a very concise summary of the PR's intent. All PRs should have a description further laying out what it accomplishes. If the PR addresses certain Github issues, those should be referenced in the body of the description so they get linked.
PRs to this repo should currently tag (or request review) from one of the following contributors:
- [Liam - @liam-fitzgerald](https://github.com/liam-fitzgerald)
- [Hunter - @arthyn](https://github.com/arthyn)
- [James - @nerveharp](https://github.com/nerveharp)
If design or visual changes are made, please provide screenshots and also tag (or request a review) from one of the following contributors:
- [Éd - @urcades](https://github.com/urcades)
- [Gavin - @g-a-v-i-n](https://github.com/g-a-v-i-n)
## Further Information
If you haven't yet, check out the main [contributing doc] at the base of the repo for information on how to get started developing on Urbit. Also you can find a host of resources on [developers.urbit.org], including ways to earn address space by contributing.
[project board]: https://github.com/orgs/urbit/projects/17
[issues]: https://github.com/urbit/landscape/issues
[weekly call]: https://github.com/urbit/landscape/issues/792
[urbit repo]: https://github.com/urbit/urbit
[contributing doc]: ../../CONTRIBUTING.md
[developers.urbit.org]: https://developers.urbit.org/

43
pkg/grid/README.md Normal file
View File

@ -0,0 +1,43 @@
# Landscape
Landscape provides the primary launching interface for Tlon's suite of userspace applications. This directory contains the front-end web application to power said interface.
Landscape is built primarily using [React], [Typescript], and [Tailwind CSS]. [Vite] ensures that all code and assets are loaded appropriately, bundles the application for distribution and provides a functional dev environment.
## Getting Started
To get started using Landscape first you need to run, `npm i && npm run bootstrap` at the top level of the greater urbit repo. This will install your npm dependencies and correctly link the current implementation of the packages at `pkg/npm/*` to your dependencies.
If you intend to edit those packages will developing on Landscape, you should also have `npm run watch-libs` running to build and re-link them after every change.
Once that's done, you can then run `npm run mock` if you'd like to get started immediately. This will use hard-coded mock data to power the interface so you can work on the interface without being connected to a ship.
To develop against a working ship, you first need to add a `.env.local` file to the root of this directory. This file will not be committed. Adding `VITE_SHIP_URL={URL}` where **{URL}** is the URL of the ship you would like to point to, will allow you to run `npm run dev`. This will proxy all requests to the ship except for those powering the interface, allowing you to see live data.
Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
## Deploying
To deploy, run `npm run build` which will bundle all the code and assets into the `dist/` folder. This can then be made into a glob by doing the following:
1. Create or launch an urbit using the -F flag
2. On that urbit, if you don't already have a desk to run from, run `|merge %work our %base` to create a new desk and mount it with `|mount %work`.
3. Now the `%work` desk is accessible through the host OS's filesystem as a directory of that urbit's pier ie `~/zod/work`.
4. From the directory of grid you can run `rsync -avL --delete dist/ ~/zod/work/grid` where `~/zod` is your fake urbit's pier.
5. Once completed you can then run `|commit %work` on your urbit and you should see your files logged back out from the dojo.
6. Now run `=dir /=garden` to switch to the garden desk directory
7. You can now run `-make-glob %work /grid` which will take the grid folder where you just added files and create a glob which can be thought of as a sort of bundle. It will be output to `~/zod/.urb/put`.
8. If you navigate to `~/zod/.urb/put` you should see a file that looks like this `glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob`. The characters between `glob-` and `.glob` are a hash of the glob's contents.
9. If you're working at Tlon, you can upload this to our Google storage using `gsutil cp glob-*.* gs://bootstrap.urbit.org`. Otherwise any publicly available HTTP endpoint that can serve files should be sufficient for distributing the glob.
10. Once you've uploaded the glob, you should then update the corresponding entry in the docket file that represents Landscape which currently resides at `pkg/garden/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
```hoon
glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
```
11. This can now be safely committed and deployed.
[react]: https://reactjs.org/
[typescript]: https://www.typescriptlang.org/
[tailwind css]: https://tailwindcss.com/
[vite]: https://vitejs.dev/

27529
pkg/grid/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
{ {
"name": "landscape", "name": "landscape",
"version": "0.0.0", "version": "0.0.0",
"private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"mock": "vite --mode mock", "mock": "vite --mode mock",
@ -21,8 +22,8 @@
"@radix-ui/react-toggle": "^0.0.10", "@radix-ui/react-toggle": "^0.0.10",
"@tlon/sigil-js": "^1.4.4", "@tlon/sigil-js": "^1.4.4",
"@types/lodash": "^4.14.172", "@types/lodash": "^4.14.172",
"@urbit/api": "^1.4.0", "@urbit/api": "^2.1.0",
"@urbit/http-api": "^1.3.1", "@urbit/http-api": "^2.1.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"clipboard-copy": "^4.0.1", "clipboard-copy": "^4.0.1",

View File

@ -1,8 +1,7 @@
import { chadIsRunning, Treaty } from '@urbit/api'; import { chadIsRunning, Treaty, Vat } from '@urbit/api';
import clipboardCopy from 'clipboard-copy'; import clipboardCopy from 'clipboard-copy';
import React, { FC, useCallback, useState } from 'react'; import React, { FC, useCallback, useState } from 'react';
import cn from 'classnames'; import cn from 'classnames';
import { Vat } from '@urbit/api/hood';
import { Button, PillButton } from './Button'; import { Button, PillButton } from './Button';
import { Dialog, DialogClose, DialogContent, DialogTrigger } from './Dialog'; import { Dialog, DialogClose, DialogContent, DialogTrigger } from './Dialog';
import { DocketHeader } from './DocketHeader'; import { DocketHeader } from './DocketHeader';
@ -137,19 +136,19 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
</div> </div>
</DocketHeader> </DocketHeader>
<div className="space-y-6"> <div className="space-y-6">
{vat ? ( {vat ? (
<> <>
<hr className="-mx-5 sm:-mx-8 border-gray-50" /> <hr className="-mx-5 sm:-mx-8 border-gray-50" />
<VatMeta vat={vat} /> <VatMeta vat={vat} />
</> </>
) : null} ) : null}
{!treaty ? null : ( {!treaty ? null : (
<> <>
<hr className="-mx-5 sm:-mx-8 border-gray-50" /> <hr className="-mx-5 sm:-mx-8 border-gray-50" />
<TreatyMeta treaty={treaty} /> <TreatyMeta treaty={treaty} />
</> </>
)} )}
</div> </div>
</div> </div>
); );
}; };

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Docket } from '@urbit/api/docket'; import { Docket } from '@urbit/api';
import cn from 'classnames'; import cn from 'classnames';
import { useTileColor } from '../tiles/useTileColor'; import { useTileColor } from '../tiles/useTileColor';

View File

@ -7,7 +7,13 @@ type ShipNameProps = {
export const ShipName = ({ name, ...props }: ShipNameProps) => { export const ShipName = ({ name, ...props }: ShipNameProps) => {
const separator = /([_^-])/; const separator = /([_^-])/;
const parts = cite(name).replace('~', '').split(separator); const citedName = cite(name);
if (!citedName) {
return null;
}
const parts = citedName.replace('~', '').split(separator);
const first = parts.shift(); const first = parts.shift();
return ( return (

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Vat } from '@urbit/api/hood'; import { Vat } from '@urbit/api';
import { Attribute } from './Attribute'; import { Attribute } from './Attribute';

View File

@ -3,7 +3,7 @@ import classNames from 'classnames';
import clipboardCopy from 'clipboard-copy'; import clipboardCopy from 'clipboard-copy';
import React, { HTMLAttributes, useCallback, useState } from 'react'; import React, { HTMLAttributes, useCallback, useState } from 'react';
import { Link, Route, useHistory } from 'react-router-dom'; import { Link, Route, useHistory } from 'react-router-dom';
import { Vat } from '@urbit/api/hood'; import { Vat } from '@urbit/api';
import { Adjust } from '../components/icons/Adjust'; import { Adjust } from '../components/icons/Adjust';
import { useVat } from '../state/kiln'; import { useVat } from '../state/kiln';
import { disableDefault, handleDropdownLink } from '../state/util'; import { disableDefault, handleDropdownLink } from '../state/util';

View File

@ -1,6 +1,6 @@
import { pick, pickBy, partition } from 'lodash'; import { pick, pickBy, partition } from 'lodash';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { kilnBump } from '@urbit/api/hood'; import { kilnBump } from '@urbit/api';
import { AppList } from '../../components/AppList'; import { AppList } from '../../components/AppList';
import { Button } from '../../components/Button'; import { Button } from '../../components/Button';
import { Dialog, DialogClose, DialogContent, DialogTrigger } from '../../components/Dialog'; import { Dialog, DialogClose, DialogContent, DialogTrigger } from '../../components/Dialog';

View File

@ -1,4 +1,4 @@
import { setMentions } from '@urbit/api/dist'; import { setMentions } from '@urbit/api';
import React from 'react'; import React from 'react';
import { Setting } from '../../components/Setting'; import { Setting } from '../../components/Setting';
import { pokeOptimisticallyN } from '../../state/base'; import { pokeOptimisticallyN } from '../../state/base';

View File

@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { import {
BigIntOrderedMap,
makePatDa, makePatDa,
decToUd, decToUd,
unixToDa, unixToDa,
@ -13,7 +14,6 @@ import {
NotificationGraphConfig, NotificationGraphConfig,
archiveAll archiveAll
} from '@urbit/api'; } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
/* eslint-disable-next-line camelcase */ /* eslint-disable-next-line camelcase */
import { unstable_batchedUpdates } from 'react-dom'; import { unstable_batchedUpdates } from 'react-dom';
import produce from 'immer'; import produce from 'immer';

View File

@ -1,5 +1,13 @@
import { getVats, Vats, scryLag, getBlockers, Vat, kilnInstall } from '@urbit/api'; import {
import { kilnPause, kilnResume } from '@urbit/api/hood'; getVats,
Vats,
scryLag,
getBlockers,
Vat,
kilnInstall,
kilnPause,
kilnResume
} from '@urbit/api';
import create from 'zustand'; import create from 'zustand';
import produce from 'immer'; import produce from 'immer';
import { useCallback } from 'react'; import { useCallback } from 'react';

View File

@ -5,7 +5,7 @@ import {
putEntry as doPutEntry, putEntry as doPutEntry,
getDeskSettings, getDeskSettings,
DeskData DeskData
} from '@urbit/api/settings'; } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { import {
BaseState, BaseState,

View File

@ -1,4 +1,4 @@
import { Docket, DocketHref, Treaty } from '@urbit/api/docket'; import { Docket, DocketHref, Treaty } from '@urbit/api';
import { hsla, parseToHsla } from 'color2k'; import { hsla, parseToHsla } from 'color2k';
import _ from 'lodash'; import _ from 'lodash';

View File

@ -71,7 +71,7 @@ module.exports = {
] ]
} }
}, },
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light|@tlon\/indigo-react)\/).*/ exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light|@tlon\/indigo-react|@urbit\/api)\/).*/
}, },
{ {
test: /\.css$/i, test: /\.css$/i,

View File

@ -30,7 +30,7 @@ module.exports = {
] ]
} }
}, },
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light|@tlon\/indigo-react)\/).*/ exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light|@tlon\/indigo-react|@urbit\/api)\/).*/
}, },
{ {
test: /\.css$/i, test: /\.css$/i,

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,8 @@
"@tlon/indigo-light": "^1.0.7", "@tlon/indigo-light": "^1.0.7",
"@tlon/indigo-react": "^1.2.27", "@tlon/indigo-react": "^1.2.27",
"@tlon/sigil-js": "^1.4.3", "@tlon/sigil-js": "^1.4.3",
"@urbit/api": "^1.4.0", "@urbit/api": "^2.1.0",
"@urbit/http-api": "^1.2.1", "@urbit/http-api": "^2.1.0",
"any-ascii": "^0.1.7", "any-ascii": "^0.1.7",
"aws-sdk": "^2.830.0", "aws-sdk": "^2.830.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",

View File

@ -1,5 +1,4 @@
import { Content, GraphNode, unixToDa } from '@urbit/api'; import { BigIntOrderedMap, Content, GraphNode, unixToDa } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
export const makeComment = ( export const makeComment = (

View File

@ -1,5 +1,4 @@
import { deSig, Path, PatpNoSig } from '@urbit/api'; import { deSig, Path, PatpNoSig, Group, Resource, roleTags, RoleTags } from '@urbit/api';
import { Group, Resource, roleTags, RoleTags } from '@urbit/api/groups';
import _ from 'lodash'; import _ from 'lodash';
export function roleForShip( export function roleForShip(

View File

@ -1,5 +1,4 @@
import { Content, GraphNode, Post, TextContent } from '@urbit/api'; import { BigIntOrderedMap, Content, GraphNode, Post, TextContent } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import { buntPost } from '~/logic/lib/post'; import { buntPost } from '~/logic/lib/post';
import { unixToDa } from '~/logic/lib/util'; import { unixToDa } from '~/logic/lib/util';

View File

@ -1,9 +1,4 @@
import { GraphNode } from '@urbit/api'; import { arrToString, stringToArr, BigIntOrderedMap, BigIntArrayOrderedMap, GraphNode } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import BigIntArrayOrderedMap, {
arrToString,
stringToArr
} from '@urbit/api/lib/BigIntArrayOrderedMap';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import produce from 'immer'; import produce from 'immer';
import _ from 'lodash'; import _ from 'lodash';

View File

@ -1,11 +1,9 @@
import { Enc } from '@urbit/api';
import { import {
Enc,
Group, Group,
GroupPolicy, GroupUpdate, GroupPolicy, GroupUpdate,
InvitePolicy, InvitePolicyDiff, OpenPolicy, OpenPolicyDiff, Tags InvitePolicy, InvitePolicyDiff, OpenPolicy, OpenPolicyDiff, Tags
} from '@urbit/api/groups'; } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import { resourceAsPath } from '../lib/util'; import { resourceAsPath } from '../lib/util';

View File

@ -1,11 +1,11 @@
import { import {
BigIntOrderedMap,
HarkPlace, HarkPlace,
Timebox, Timebox,
HarkStats, HarkStats,
harkBinToId, harkBinToId,
makePatDa makePatDa
} from '@urbit/api'; } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import _ from 'lodash'; import _ from 'lodash';
import { compose } from 'lodash/fp'; import { compose } from 'lodash/fp';
import { BaseState } from '../state/base'; import { BaseState } from '../state/base';

View File

@ -1,4 +1,4 @@
import { InviteUpdate } from '@urbit/api/invite'; import { InviteUpdate } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { BaseState } from '../state/base'; import { BaseState } from '../state/base';
import { InviteState as State } from '../state/invite'; import { InviteState as State } from '../state/invite';

View File

@ -1,4 +1,4 @@
import { MetadataUpdate, Associations, ResourceAssociations } from '@urbit/api/metadata'; import { MetadataUpdate, Associations, ResourceAssociations } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import { BaseState } from '../state/base'; import { BaseState } from '../state/base';

View File

@ -1,4 +1,4 @@
import { SettingsUpdate } from '@urbit/api/settings'; import { SettingsUpdate } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { SettingsState as State } from '~/logic/state/settings'; import { SettingsState as State } from '~/logic/state/settings';
import { BaseState } from '../state/base'; import { BaseState } from '../state/base';

View File

@ -1,12 +1,12 @@
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import { patp2dec } from 'urbit-ob'; import { patp2dec } from 'urbit-ob';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import {
import { Association, deSig, GraphNode, Graphs, FlatGraphs, resourceFromPath, ThreadGraphs, getGraph, getShallowChildren, setScreen } from '@urbit/api'; Association, BigIntOrderedMap, deSig, GraphNode, Graphs, FlatGraphs, resourceFromPath, ThreadGraphs, getGraph, getShallowChildren, setScreen,
addDmMessage, addPost, Content, getDeepOlderThan, getFirstborn, getNewest, getNode, getOlderSiblings, getYoungerSiblings, markPending, Post, addNode, GraphNodePoke
} from '@urbit/api';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { createState, createSubscription, reduceStateN, pokeOptimisticallyN } from './base'; import { createState, createSubscription, reduceStateN, pokeOptimisticallyN } from './base';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import { addDmMessage, addPost, Content, getDeepOlderThan, getFirstborn, getNewest, getNode, getOlderSiblings, getYoungerSiblings, markPending, Post, addNode, GraphNodePoke } from '@urbit/api/graph';
import { GraphReducer, reduceDm } from '../reducers/graph-update'; import { GraphReducer, reduceDm } from '../reducers/graph-update';
import _ from 'lodash'; import _ from 'lodash';
import { clone } from '../lib/util'; import { clone } from '../lib/util';

View File

@ -1,5 +1,6 @@
import { import {
archive, archive,
BigIntOrderedMap,
HarkBin, HarkBin,
markCountAsRead, markCountAsRead,
NotificationGraphConfig, NotificationGraphConfig,
@ -15,7 +16,6 @@ import {
import { Poke } from '@urbit/http-api'; import { Poke } from '@urbit/http-api';
import { patp2dec } from 'urbit-ob'; import { patp2dec } from 'urbit-ob';
import _ from 'lodash'; import _ from 'lodash';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import api from '~/logic/api'; import api from '~/logic/api';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';

View File

@ -1,4 +1,4 @@
import { Association, Associations, MetadataUpdatePreview } from '@urbit/api/metadata'; import { Association, Associations, MetadataUpdatePreview } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { import {

View File

@ -16,8 +16,7 @@ import {
import { useCallback } from 'react'; import { useCallback } from 'react';
import { reduceUpdate } from '../reducers/settings-update'; import { reduceUpdate } from '../reducers/settings-update';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import { Contact, getDeskSettings, Value } from '@urbit/api'; import { Contact, getDeskSettings, putEntry, Value } from '@urbit/api';
import { putEntry } from '@urbit/api/settings';
export interface ShortcutMapping { export interface ShortcutMapping {
cycleForward: string; cycleForward: string;

View File

@ -3,7 +3,7 @@ import { Meta, Story } from '@storybook/react';
import { Box } from '@tlon/indigo-react'; import { Box } from '@tlon/indigo-react';
import { InviteItem, InviteItemProps } from '~/views/components/Invite'; import { InviteItem, InviteItemProps } from '~/views/components/Invite';
import { JoinProgress } from '@urbit/api/groups'; import { JoinProgress } from '@urbit/api';
export default { export default {
title: 'Notifications/Invite', title: 'Notifications/Invite',

View File

@ -4,8 +4,7 @@ import { withDesign } from 'storybook-addon-designs';
import { Col, Row } from '@tlon/indigo-react'; import { Col, Row } from '@tlon/indigo-react';
import { LinkBlockItem } from '~/views/apps/links/components/LinkBlockItem'; import { LinkBlockItem } from '~/views/apps/links/components/LinkBlockItem';
import { createPost, GraphNode } from '@urbit/api'; import { BigIntOrderedMap, createPost, GraphNode } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
export default { export default {
title: 'Collections/BlockItem', title: 'Collections/BlockItem',

View File

@ -4,8 +4,7 @@ import { withDesign } from 'storybook-addon-designs';
import { Box } from '@tlon/indigo-react'; import { Box } from '@tlon/indigo-react';
import { LinkDetail } from '~/views/apps/links/components/LinkDetail'; import { LinkDetail } from '~/views/apps/links/components/LinkDetail';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap'; import { BigIntOrderedMap, GraphNode } from '@urbit/api';
import { GraphNode } from '@urbit/api';
import useMetadataState from '~/logic/state/metadata'; import useMetadataState from '~/logic/state/metadata';
import { makeComment } from '~/logic/lib/fixtures'; import { makeComment } from '~/logic/lib/fixtures';

View File

@ -1,5 +1,4 @@
import { ContactUpdate, GroupUpdate, InviteUpdate, MetadataUpdate } from '@urbit/api'; import { ContactUpdate, GroupUpdate, InviteUpdate, MetadataUpdate, SettingsUpdate } from '@urbit/api';
import { SettingsUpdate } from '@urbit/api/settings';
import { ConnectionStatus } from './connection'; import { ConnectionStatus } from './connection';
import { LaunchUpdate, WeatherState } from './launch-update'; import { LaunchUpdate, WeatherState } from './launch-update';
import { LocalUpdate } from './local-update'; import { LocalUpdate } from './local-update';

View File

@ -1,5 +1,4 @@
import { Content, createPost, fetchIsAllowed, Post, removePosts, deSig } from '@urbit/api'; import { Association, Content, createPost, fetchIsAllowed, Post, removePosts, deSig } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import { BigInteger } from 'big-integer'; import { BigInteger } from 'big-integer';
import React, { import React, {
ReactElement, useCallback, ReactElement, useCallback,

View File

@ -1,6 +1,5 @@
import { Box, Center, Col, LoadingSpinner, Text } from '@tlon/indigo-react'; import { Box, Center, Col, LoadingSpinner, Text } from '@tlon/indigo-react';
import { deSig, Group } from '@urbit/api'; import { Association, deSig, Group } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import bigInt from 'big-integer'; import bigInt from 'big-integer';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Link, Route, Switch, useLocation } from 'react-router-dom'; import { Link, Route, Switch, useLocation } from 'react-router-dom';

View File

@ -1,12 +1,11 @@
import { Col, Row, Text } from '@tlon/indigo-react'; import { Col, Row, Text } from '@tlon/indigo-react';
import { Association, Graph, GraphNode, markEachAsRead } from '@urbit/api'; import { Association, BigIntOrderedMap, Graph, GraphNode, markEachAsRead } from '@urbit/api';
import React, { useCallback, useState, useMemo, useEffect } from 'react'; import React, { useCallback, useState, useMemo, useEffect } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import { useResize } from '~/logic/lib/useResize'; import { useResize } from '~/logic/lib/useResize';
import { LinkBlockItem } from './LinkBlockItem'; import { LinkBlockItem } from './LinkBlockItem';
import { LinkBlockInput } from './LinkBlockInput'; import { LinkBlockInput } from './LinkBlockInput';
import useLocalState from '~/logic/state/local'; import useLocalState from '~/logic/state/local';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt from 'big-integer'; import bigInt from 'big-integer';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import useHarkState, { selHarkGraph } from '~/logic/state/hark'; import useHarkState, { selHarkGraph } from '~/logic/state/hark';

View File

@ -3,7 +3,7 @@ import { Box, Row, Text } from '@tlon/indigo-react';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import Author from '~/views/components/Author'; import Author from '~/views/components/Author';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { acceptDm, declineDm } from '@urbit/api/graph'; import { acceptDm, declineDm } from '@urbit/api';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
export function PendingDm(props: { ship: string; }) { export function PendingDm(props: { ship: string; }) {

View File

@ -1,6 +1,5 @@
import { Box, Col, Icon, Image, Row, Text } from '@tlon/indigo-react'; import { Box, Col, Icon, Image, Row, Text } from '@tlon/indigo-react';
import { Group } from '@urbit/api'; import { Group, GraphNode } from '@urbit/api';
import { GraphNode } from '@urbit/api/graph';
import React from 'react'; import React from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';

View File

@ -1,5 +1,4 @@
import { addNodes, Association } from '@urbit/api'; import { addNodes, Association, Graph } from '@urbit/api';
import { Graph } from '@urbit/api/graph';
import { FormikHelpers } from 'formik'; import { FormikHelpers } from 'formik';
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';

View File

@ -1,6 +1,5 @@
import { Action, Box, Col, Icon, Row, Text } from '@tlon/indigo-react'; import { Action, Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
import { Group, removePosts } from '@urbit/api'; import { GraphNode, Group, removePosts } from '@urbit/api';
import { GraphNode } from '@urbit/api/graph';
import bigInt from 'big-integer'; import bigInt from 'big-integer';
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { roleForShip } from '~/logic/lib/group'; import { roleForShip } from '~/logic/lib/group';

View File

@ -6,8 +6,7 @@ import {
ErrorLabel, Icon, Label, ErrorLabel, Icon, Label,
Row, Text Row, Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { OpenPolicy } from '@urbit/api'; import { Association, OpenPolicy } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import { FieldArray, useFormikContext } from 'formik'; import { FieldArray, useFormikContext } from 'formik';
import _ from 'lodash'; import _ from 'lodash';
import React, { ReactElement, useMemo, useState } from 'react'; import React, { ReactElement, useMemo, useState } from 'react';

View File

@ -1,5 +1,4 @@
import { JoinRequest } from '@urbit/api'; import { Invite, JoinRequest } from '@urbit/api';
import { Invite } from '@urbit/api/invite';
import React from 'react'; import React from 'react';
import { usePreview } from '~/logic/state/metadata'; import { usePreview } from '~/logic/state/metadata';
import { GroupInvite } from './Group'; import { GroupInvite } from './Group';

View File

@ -1,9 +1,7 @@
import { BigInteger } from 'big-integer'; import { BigInteger } from 'big-integer';
import React from 'react'; import React from 'react';
import VirtualScroller, { VirtualScrollerProps } from './VirtualScroller'; import VirtualScroller, { VirtualScrollerProps } from './VirtualScroller';
import { arrToString, FlatGraphNode } from '@urbit/api';
import { arrToString } from '@urbit/api/lib/BigIntArrayOrderedMap';
import { FlatGraphNode } from '@urbit/api';
type ThreadScrollerProps = Omit< type ThreadScrollerProps = Omit<
VirtualScrollerProps<BigInteger[], FlatGraphNode>, VirtualScrollerProps<BigInteger[], FlatGraphNode>,

View File

@ -1,6 +1,5 @@
import { Box, Center, Col, Text } from '@tlon/indigo-react'; import { Box, Center, Col, Text } from '@tlon/indigo-react';
import { joinGraph } from '@urbit/api/graph'; import { Association, GraphConfig, joinGraph } from '@urbit/api';
import { Association, GraphConfig } from '@urbit/api/metadata';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useQuery } from '~/logic/lib/useQuery'; import { useQuery } from '~/logic/lib/useQuery';

View File

@ -5,9 +5,7 @@ import {
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import _ from 'lodash'; import _ from 'lodash';
import { changePolicy, deSig, Enc } from '@urbit/api'; import { Association, changePolicy, deSig, Enc, Group, GroupPolicy, metadataEdit, MetadataEditField } from '@urbit/api';
import { Group, GroupPolicy } from '@urbit/api/groups';
import { Association, metadataEdit, MetadataEditField } from '@urbit/api/metadata';
import { Form, Formik, FormikHelpers } from 'formik'; import { Form, Formik, FormikHelpers } from 'formik';
import React from 'react'; import React from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';

View File

@ -1,7 +1,5 @@
import { Box, Button, Col, Text } from '@tlon/indigo-react'; import { Box, Button, Col, Text } from '@tlon/indigo-react';
import { deSig } from '@urbit/api'; import { Association, deSig, Group } from '@urbit/api';
import { Group } from '@urbit/api/groups';
import { Association } from '@urbit/api/metadata';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { resourceFromPath, roleForShip } from '~/logic/lib/group'; import { resourceFromPath, roleForShip } from '~/logic/lib/group';

View File

@ -4,8 +4,7 @@ import {
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { ignoreGroup, listenGroup } from '@urbit/api'; import { Association, ignoreGroup, listenGroup } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import React from 'react'; import React from 'react';
import useHarkState from '~/logic/state/hark'; import useHarkState from '~/logic/state/hark';
import { StatelessAsyncToggle } from '~/views/components/StatelessAsyncToggle'; import { StatelessAsyncToggle } from '~/views/components/StatelessAsyncToggle';

View File

@ -1,5 +1,5 @@
import { Button, Icon, Row, Text } from '@tlon/indigo-react'; import { Button, Icon, Row, Text } from '@tlon/indigo-react';
import { disableGroupFeed } from '@urbit/api/graph'; import { disableGroupFeed } from '@urbit/api';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { resourceFromPath } from '~/logic/lib/group'; import { resourceFromPath } from '~/logic/lib/group';

View File

@ -1,12 +1,9 @@
import { Box, Col } from '@tlon/indigo-react'; import { Box, Col } from '@tlon/indigo-react';
import { Association, FlatGraph, FlatGraphNode, Group } from '@urbit/api'; import { arrToString, Association, FlatGraph, FlatGraphNode, Group } from '@urbit/api';
import bigInt from 'big-integer'; import bigInt from 'big-integer';
import React from 'react'; import React from 'react';
import { RouteComponentProps, useHistory } from 'react-router'; import { RouteComponentProps, useHistory } from 'react-router';
import { resourceFromPath } from '~/logic/lib/group'; import { resourceFromPath } from '~/logic/lib/group';
import {
arrToString
} from '@urbit/api/lib/BigIntArrayOrderedMap';
import { keyEq, ThreadScroller } from '~/views/components/ThreadScroller'; import { keyEq, ThreadScroller } from '~/views/components/ThreadScroller';
import PostItem from './PostItem/PostItem'; import PostItem from './PostItem/PostItem';
import PostInput from './PostInput'; import PostInput from './PostInput';

View File

@ -1,5 +1,5 @@
import { Action, Col, Icon, Row } from '@tlon/indigo-react'; import { Action, Col, Icon, Row } from '@tlon/indigo-react';
import { Association, Post } from '@urbit/api'; import { Association, Post, removePosts } from '@urbit/api';
import React, { ReactElement } from 'react'; import React, { ReactElement } from 'react';
import { getPermalinkForGraph } from '~/logic/lib/permalinks'; import { getPermalinkForGraph } from '~/logic/lib/permalinks';
import { useCopy } from '~/logic/lib/useCopy'; import { useCopy } from '~/logic/lib/useCopy';
@ -8,7 +8,6 @@ import { resourceFromPath } from '~/logic/lib/group';
import Author from '~/views/components/Author'; import Author from '~/views/components/Author';
import { Dropdown } from '~/views/components/Dropdown'; import { Dropdown } from '~/views/components/Dropdown';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import { removePosts } from '@urbit/api/graph';
interface PostHeaderProps { interface PostHeaderProps {
post: Post; post: Post;
association: Association; association: Association;

View File

@ -6,11 +6,10 @@ import React, {
} from 'react'; } from 'react';
import { resourceFromPath } from '~/logic/lib/group'; import { resourceFromPath } from '~/logic/lib/group';
import { Loading } from '~/views/components/Loading'; import { Loading } from '~/views/components/Loading';
import { arrToString } from '@urbit/api/lib/BigIntArrayOrderedMap';
import useGraphState from '~/logic/state/graph'; import useGraphState from '~/logic/state/graph';
import PostFlatFeed from './PostFlatFeed'; import PostFlatFeed from './PostFlatFeed';
import PostInput from './PostInput'; import PostInput from './PostInput';
import { Association, deSig, PermVariation } from '@urbit/api'; import { arrToString, Association, deSig, PermVariation } from '@urbit/api';
import { useParams, Switch, Route } from 'react-router'; import { useParams, Switch, Route } from 'react-router';
import { useGroupForAssoc } from '~/logic/state/group'; import { useGroupForAssoc } from '~/logic/state/group';

View File

@ -5,8 +5,7 @@ import {
Row, Text Row, Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { invite } from '@urbit/api/groups'; import { Association, invite } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import { Form, Formik } from 'formik'; import { Form, Formik } from 'formik';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useRef } from 'react'; import React, { useCallback, useRef } from 'react';

View File

@ -1,5 +1,5 @@
import { Box, Col, Text } from '@tlon/indigo-react'; import { Box, Col, Text } from '@tlon/indigo-react';
import { invite } from '@urbit/api/groups'; import { invite } from '@urbit/api';
import { Form, Formik } from 'formik'; import { Form, Formik } from 'formik';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';

View File

@ -6,10 +6,7 @@ import {
StatelessTextInput as Input, Text StatelessTextInput as Input, Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { Contact, Contacts } from '@urbit/api/contacts'; import { addTag, Association, Contact, Contacts, changePolicy, deSig, Group, removeMembers, removeTag, RoleTags } from '@urbit/api';
import { addTag, removeMembers, changePolicy, Group, removeTag, RoleTags } from '@urbit/api/groups';
import { Association } from '@urbit/api/metadata';
import { deSig } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import f from 'lodash/fp'; import f from 'lodash/fp';
import React, { import React, {

View File

@ -1,7 +1,5 @@
import { Box, Col, Text } from '@tlon/indigo-react'; import { Box, Col, Text } from '@tlon/indigo-react';
import { Group } from '@urbit/api/groups'; import { Association, deSig, Group } from '@urbit/api';
import { deSig } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import React, { ReactElement, useCallback, useRef } from 'react'; import React, { ReactElement, useCallback, useRef } from 'react';
import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom';
import { resourceFromPath } from '~/logic/lib/group'; import { resourceFromPath } from '~/logic/lib/group';

View File

@ -1,5 +1,4 @@
import { Association } from '@urbit/api/metadata'; import { AppName, Association } from '@urbit/api';
import { AppName } from '@urbit/api';
import React, { ReactElement } from 'react'; import React, { ReactElement } from 'react';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';

View File

@ -1,7 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import { Box, Col, Icon, Text } from '@tlon/indigo-react'; import { Box, Col, Icon, Text } from '@tlon/indigo-react';
import { Association } from '@urbit/api/metadata'; import { AppName, Association } from '@urbit/api';
import { AppName } from '@urbit/api';
import React, { ReactElement, ReactNode, useCallback, useState } from 'react'; import React, { ReactElement, ReactNode, useCallback, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';

12
pkg/npm/api/.babelrc Normal file
View File

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

1
pkg/npm/api/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tmp

View File

@ -1,5 +1,5 @@
declare module "urbit-ob" { declare module 'urbit-ob' {
/** /**
* Convert a @p-encoded string to a decimal-encoded string. * Convert a @p-encoded string to a decimal-encoded string.

View File

@ -1,6 +1,6 @@
import { Patp } from '../lib'; import { Patp } from '../lib';
import BigIntOrderedMap from '../lib/BigIntOrderedMap'; import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
import BigIntArrayOrderedMap from '../lib/BigIntArrayOrderedMap'; import { BigIntArrayOrderedMap } from '../lib/BigIntArrayOrderedMap';
export interface TextContent { export interface TextContent {
text: string; text: string;

View File

@ -1,5 +1,3 @@
import _ from 'lodash';
import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types'; 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 { Group, GroupPolicy, GroupPolicyDiff, GroupUpdateAddMembers, GroupUpdateAddTag, GroupUpdateChangePolicy, GroupUpdateRemoveGroup, GroupUpdateRemoveMembers, GroupUpdateRemoveTag, Resource, RoleTags, Tag } from './types';
import { GroupUpdate } from './update'; import { GroupUpdate } from './update';
@ -97,6 +95,10 @@ export const changePolicy = (
} }
}); });
export const makeResource = (ship: string, name: string) => {
return { ship, name };
};
export const join = ( export const join = (
ship: string, ship: string,
name: string, name: string,
@ -181,30 +183,32 @@ export const roleForShip = (
const roleShips = group?.tags?.role?.[role]; const roleShips = group?.tags?.role?.[role];
return roleShips && roleShips.has(ship) ? role : currRole; return roleShips && roleShips.has(ship) ? role : currRole;
}, undefined as RoleTags | undefined); }, undefined as RoleTags | undefined);
} };
export const resourceFromPath = (path: Path): Resource => { export const resourceFromPath = (path: Path): Resource => {
const [, , ship, name] = path.split('/'); const [, , ship, name] = path.split('/');
return { ship, name }; return { ship, name };
} };
export const makeResource = (ship: string, name: string) => {
return { ship, name };
}
export const isWriter = (group: Group, resource: string, ship: string) => { export const isWriter = (group: Group, resource: string, ship: string) => {
const writers: Set<string> | undefined = _.get( const graph = group.tags?.graph;
group, const writers: Set<string> | undefined = graph && (graph[resource] as any)?.writers;
['tags', 'graph', resource, 'writers'],
undefined
);
const admins = group?.tags?.role?.admin ?? new Set(); const admins = group?.tags?.role?.admin ?? new Set();
if (_.isUndefined(writers)) { if (typeof writers === 'undefined') {
return true; return true;
} else { } else {
return writers.has(ship) || admins.has(ship); return writers.has(ship) || admins.has(ship);
} }
} };
export const isHost = (
resource: string,
ship: string
): boolean => {
const [, , host] = resource.split('/');
return ship === host;
};
export const isChannelAdmin = ( export const isChannelAdmin = (
group: Group, group: Group,
@ -218,13 +222,4 @@ export const isChannelAdmin = (
role === 'admin' || role === 'admin' ||
role === 'moderator' role === 'moderator'
); );
} };
export const isHost = (
resource: string,
ship: string
): boolean => {
const [, , host] = resource.split('/');
return ship === host;
}

View File

@ -1,4 +1,4 @@
import { PatpNoSig, Path, Jug, ShipRank, Enc } from '../lib'; import { PatpNoSig, Path, ShipRank, Enc } from '../lib';
import { roleTags } from './index'; import { roleTags } from './index';
export type RoleTags = typeof roleTags[number]; export type RoleTags = typeof roleTags[number];
@ -173,5 +173,3 @@ export type GroupUpdate =
| GroupUpdateInitialGroup; | GroupUpdateInitialGroup;
export type GroupAction = Omit<GroupUpdate, 'initialGroup' | 'initial'>; export type GroupAction = Omit<GroupUpdate, 'initialGroup' | 'initial'>;

View File

@ -1,6 +1,5 @@
import { Poke, Scry } from '../lib'; import { Poke, Scry } from '../lib';
import { Vats, Vat } from './types'; import { Vats, Vat } from './types';
import _ from 'lodash';
export const getVats: Scry = { export const getVats: Scry = {
app: 'hood', app: 'hood',
@ -98,13 +97,17 @@ export function getBlockers(vats: Vats): string[] {
if(!blockedOn) { if(!blockedOn) {
return blockers; return blockers;
} }
_.forEach(_.omit(vats, 'base'), (vat, desk) => {
// assuming only %zuse Object.entries(vats)
const kelvins = _.map((vat.arak.rail?.next || []), n => n.weft.kelvin); .filter(([desk]) => desk !== 'base')
if(!(kelvins.includes(blockedOn))) { .forEach(([desk, vat]) => {
blockers.push(desk); // assuming only %zuse
} const woofs = vat.arak.rail?.next || [];
}); const kelvins = woofs.map(n => n.weft.kelvin);
if(!(kelvins.includes(blockedOn))) {
blockers.push(desk);
}
});
return blockers; return blockers;
} }

View File

@ -7,7 +7,8 @@ export * as groups from './groups';
export * from './hark'; export * from './hark';
export * as hark from './hark'; export * as hark from './hark';
export * from './invite'; export * from './invite';
export * as invite from './invite'; // this conflicts with /groups/lib invite
// export * as invite from './invite';
export * from './metadata'; export * from './metadata';
export * as metadata from './metadata'; export * as metadata from './metadata';
export * from './settings'; export * from './settings';
@ -16,6 +17,7 @@ export * from './s3';
export * as s3 from './s3'; export * as s3 from './s3';
export * from './lib'; export * from './lib';
export * from './lib/BigIntOrderedMap'; export * from './lib/BigIntOrderedMap';
export * from './lib/BigIntArrayOrderedMap';
export * as hood from './hood'; export * as hood from './hood';
export * from './hood'; export * from './hood';
export * as docket from './docket'; export * as docket from './docket';

View File

@ -51,7 +51,7 @@ export function sortBigIntArr(a: BigInteger[], b: BigInteger[]) {
return bLen - aLen; return bLen - aLen;
} }
export default class BigIntArrayOrderedMap<V> implements Iterable<[BigInteger[], V]> { export class BigIntArrayOrderedMap<V> implements Iterable<[BigInteger[], V]> {
root: Record<string, V> = {} root: Record<string, V> = {}
cachedIter: [BigInteger[], V][] | null = null; cachedIter: [BigInteger[], V][] | null = null;
[immerable] = true; [immerable] = true;

View File

@ -14,7 +14,7 @@ function sortBigInt(a: BigInteger, b: BigInteger) {
return -1; return -1;
} }
} }
export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> { export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
root: Record<string, V> = {} root: Record<string, V> = {}
cachedIter: [BigInteger, V][] | null = null; cachedIter: [BigInteger, V][] | null = null;
[immerable] = true; [immerable] = true;

View File

@ -1,5 +1,3 @@
import _ from "lodash";
import f from "lodash/fp";
import bigInt, { BigInteger } from "big-integer"; import bigInt, { BigInteger } from "big-integer";
import { Resource } from "../groups/types"; import { Resource } from "../groups/types";
@ -9,6 +7,36 @@ const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud
const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1 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. * Given a bigint representing an urbit date, returns a unix timestamp.
* *
@ -48,17 +76,11 @@ export function udToDec(ud: string): string {
} }
export function decToUd(str: string): string { export function decToUd(str: string): string {
return _.trimStart( const transform = chunk(str.split('').reverse(), 3)
f.flow( .map(group => group.reverse().join(''))
f.split(""), .reverse()
f.reverse, .join('.')
f.chunk(3), return transform.replace(/^[0\.]+/g, '');
f.map(f.flow(f.reverse, f.join(""))),
f.reverse,
f.join(".")
)(str),
"0."
);
} }
export function resourceAsPath(resource: Resource): string { export function resourceAsPath(resource: Resource): string {
@ -161,14 +183,11 @@ export function uxToHex(ux: string) {
} }
export const hexToUx = (hex: string): string => { export const hexToUx = (hex: string): string => {
const ux = f.flow( const nonZeroChars = dropWhile(hex.split(''), y => y === '0');
f.dropWhile(y => y === '0'), const ux = chunk(nonZeroChars.reverse(), 4).map(x => {
f.reverse, return x.reverse().join('');
f.chunk(4), }).reverse().join('.') || '0';
f.map(x => x.reverse().join('')),
f.reverse,
f.join('.')
)(hex.split('')) || '0';
return `0x${ux}`; return `0x${ux}`;
}; };
@ -210,21 +229,6 @@ export function stringToTa(str: string): string {
return "~." + out; return "~." + out;
} }
/**
* Formats a numbers as a `@ud` inserting dot where needed
*/
export function numToUd(num: number): string {
return f.flow(
f.split(''),
f.reverse,
f.chunk(3),
f.reverse,
f.map(s => s.join('')),
f.join('.')
)(num.toString())
}
export const buntPost = (): Post => ({ export const buntPost = (): Post => ({
author: '', author: '',
contents: [], contents: [],

View File

@ -2,7 +2,7 @@
* Martian embassy * Martian embassy
*/ */
import BigIntOrderedMap from "./BigIntOrderedMap"; import { BigIntOrderedMap } from "./BigIntOrderedMap";
// an urbit style path rendered as string // an urbit style path rendered as string
export type Path = string; export type Path = string;

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,52 @@
{ {
"name": "@urbit/api", "name": "@urbit/api",
"version": "1.4.1", "version": "2.1.0",
"description": "", "description": "A library that provides bindings and types for Urbit's various userspace desks",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "ssh://git@github.com/urbit/urbit.git", "url": "ssh://git@github.com/urbit/urbit.git",
"directory": "pkg/npm/api" "directory": "pkg/npm/api"
}, },
"main": "dist/index.js", "type": "module",
"types": "dist/index.d", "main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"jsdelivr": "dist/urbit-api.min.js",
"unpkg": "dist/urbit-api.min.js",
"types": "dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": { "scripts": {
"test": "echo \"No test specified\" && exit 0", "test": "echo \"No test specified\" && exit 0",
"watch": "tsc -p tsconfig.json --watch", "build": "npm run clean && rollup -c && npx tsc -p tsconfig.json",
"build": "tsc -p tsconfig.json", "prepare": "npm run build",
"clean": "rm -rf dist/*", "watch": "rollup -c -w",
"prepare": "npm run build" "clean": "rm -rf dist/* types/*"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.16.0",
"@types/lodash": "^4.14.168",
"@urbit/eslint-config": "^1.0.3",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
"core-js": "^3.19.1",
"immer": "^9.0.1", "immer": "^9.0.1",
"lodash": "^4.17.20",
"urbit-ob": "^5.0.1" "urbit-ob": "^5.0.1"
}, },
"devDependencies": { "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", "@types/node": "^15.12.5",
"@typescript-eslint/eslint-plugin": "^4.28.2", "@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2", "@typescript-eslint/parser": "^4.28.2",
"@urbit/eslint-config": "^1.0.3",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint-plugin-react": "^7.24.0", "eslint-plugin-react": "^7.24.0",
"onchange": "^7.1.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" "typescript": "^4.3.2"
} }
} }

View File

@ -0,0 +1,76 @@
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: [
{
dir: 'dist/esm',
format: 'esm',
exports: 'named',
sourcemap: true
},
{
dir: 'dist/cjs',
format: 'cjs',
exports: 'named',
sourcemap: true
}
]
}
];

View File

@ -1,10 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"outDir": "./dist", "outDir": "./tmp",
"module": "ES2020", "module": "ESNext",
"noImplicitAny": true, "noImplicitAny": true,
"target": "ES2017", "target": "ESNext",
"pretty": true, "pretty": true,
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
@ -14,6 +14,11 @@
"strict": false, "strict": false,
"noErrorTruncation": true "noErrorTruncation": true
}, },
"exclude": ["node_modules", "./dist/**/*", "@types"], "exclude": [
"node_modules",
"./dist/**/*",
"./tmp/**/*",
"rollup.config.ts"
],
"include": ["./*.ts"] "include": ["./*.ts"]
} }

13
pkg/npm/http-api/.babelrc Normal file
View File

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

View File

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

View File

@ -1,6 +0,0 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};

View File

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

View File

@ -23,7 +23,7 @@ module.exports = {
// collectCoverageFrom: undefined, // collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files // The directory where Jest should output its coverage files
coverageDirectory: "coverage", coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection // An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [ // coveragePathIgnorePatterns: [
@ -137,7 +137,7 @@ module.exports = {
// snapshotSerializers: [], // snapshotSerializers: [],
// The test environment that will be used for testing // The test environment that will be used for testing
testEnvironment: "jsdom", testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment // Options that will be passed to the testEnvironment
// testEnvironmentOptions: {}, // testEnvironmentOptions: {},
@ -166,7 +166,7 @@ module.exports = {
// testRunner: "jest-circus/runner", // testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
testURL: "http://localhost", testURL: 'http://localhost',
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real", // timers: "real",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/http-api", "name": "@urbit/http-api",
"version": "1.3.1", "version": "2.1.0",
"license": "MIT", "license": "MIT",
"description": "Library to interact with an Urbit ship over HTTP", "description": "Library to interact with an Urbit ship over HTTP",
"repository": { "repository": {
@ -8,18 +8,21 @@
"url": "ssh://git@github.com/urbit/urbit.git", "url": "ssh://git@github.com/urbit/urbit.git",
"directory": "pkg/npm/http-api" "directory": "pkg/npm/http-api"
}, },
"main": "dist/index.js", "type": "module",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"jsdelivr": "dist/urbit-http-api.min.js",
"unpkg": "dist/urbit-http-api.min.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"files": [ "files": [
"dist", "dist/**"
"src"
], ],
"scripts": { "scripts": {
"test": "jest", "test": "jest",
"build": "tsc -p tsconfig.json", "build": "npm run clean && rollup -c && npx tsc -p tsconfig.json",
"prepare": "npm run build", "prepare": "npm run build",
"watch": "tsc -p tsconfig.json --watch", "watch": "rollup -c -w",
"clean": "rm -rf dist/*" "clean": "rm -rf dist/* types/*"
}, },
"prettier": { "prettier": {
"printWidth": 80, "printWidth": 80,
@ -29,12 +32,12 @@
}, },
"author": "", "author": "",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.6", "@babel/core": "^7.15.8",
"@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/preset-env": "^7.15.8",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1", "@babel/preset-typescript": "^7.16.0",
"@babel/plugin-proposal-optional-chaining": "^7.12.1", "@rollup/plugin-babel": "^5.3.0",
"@babel/preset-env": "^7.14.7", "@rollup/plugin-commonjs": "^21.0.1",
"@babel/preset-typescript": "^7.12.1", "@rollup/plugin-node-resolve": "^13.0.6",
"@types/browser-or-node": "^1.2.0", "@types/browser-or-node": "^1.2.0",
"@types/eventsource": "^1.1.5", "@types/eventsource": "^1.1.5",
"@types/jest": "^26.0.24", "@types/jest": "^26.0.24",
@ -42,29 +45,22 @@
"@typescript-eslint/eslint-plugin": "^4.7.0", "@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0", "@typescript-eslint/parser": "^4.7.0",
"babel-jest": "^27.0.6", "babel-jest": "^27.0.6",
"babel-loader": "^8.2.1",
"clean-webpack-plugin": "^3.0.0",
"cross-fetch": "^3.1.4", "cross-fetch": "^3.1.4",
"event-target-polyfill": "0.0.3", "event-target-polyfill": "0.0.3",
"fast-text-encoding": "^1.0.3", "fast-text-encoding": "^1.0.3",
"jest": "^27.0.6", "jest": "^27.0.6",
"onchange": "^7.1.0", "rollup": "^2.59.0",
"tslib": "^2.0.3", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^3.9.7", "typescript": "^3.9.7",
"util": "^0.12.3", "util": "^0.12.3",
"web-streams-polyfill": "^3.0.3", "web-streams-polyfill": "^3.0.3",
"webpack": "^5.4.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"yet-another-abortcontroller-polyfill": "0.0.4" "yet-another-abortcontroller-polyfill": "0.0.4"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@microsoft/fetch-event-source": "^2.0.0", "@microsoft/fetch-event-source": "^2.0.0",
"browser-or-node": "^1.3.0", "browser-or-node": "^1.3.0",
"browserify-zlib": "^0.2.0", "core-js": "^3.19.1"
"buffer": "^6.0.3",
"stream-browserify": "^3.0.0",
"stream-http": "^3.1.1"
} }
} }

View File

@ -0,0 +1,72 @@
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: [
{
dir: 'dist/esm',
format: 'esm',
exports: 'named',
sourcemap: true,
},
{
dir: 'dist/cjs',
format: 'cjs',
exports: 'named',
sourcemap: true,
},
],
},
];

View File

@ -1,8 +1,21 @@
import { isBrowser, isNode } from 'browser-or-node'; import { isBrowser, isNode } from 'browser-or-node';
import { fetchEventSource, EventSourceMessage, EventStreamContentType } from '@microsoft/fetch-event-source'; import {
fetchEventSource,
EventSourceMessage,
} from '@microsoft/fetch-event-source';
import { Action, Scry, Thread, AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, SSEOptions, PokeHandlers, Message, FatalError } from './types'; import {
import { uncamelize, hexString } from './utils'; Scry,
Thread,
AuthenticationInterface,
PokeInterface,
SubscriptionRequestInterface,
headers,
SSEOptions,
PokeHandlers,
Message,
} from './types';
import { hexString } from './utils';
/** /**
* A class for interacting with an urbit ship, given its URL and code * A class for interacting with an urbit ship, given its URL and code
@ -32,7 +45,7 @@ export class Urbit {
/** /**
* A registry of requestId to successFunc/failureFunc * A registry of requestId to successFunc/failureFunc
* *
* These functions are registered during a +poke and are executed * These functions are registered during a +poke and are executed
* in the onServerEvent()/onServerError() callbacks. Only one of * in the onServerEvent()/onServerError() callbacks. Only one of
* the functions will be called, and the outstanding poke will be * the functions will be called, and the outstanding poke will be
@ -43,16 +56,17 @@ export class Urbit {
/** /**
* A registry of requestId to subscription functions. * A registry of requestId to subscription functions.
* *
* These functions are registered during a +subscribe and are * These functions are registered during a +subscribe and are
* executed in the onServerEvent()/onServerError() callbacks. The * executed in the onServerEvent()/onServerError() callbacks. The
* event function will be called whenever a new piece of data on this * 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 * subscription is available, which may be 0, 1, or many times. The
* disconnect function may be called exactly once. * disconnect function may be called exactly once.
*/ */
private outstandingSubscriptions: Map<number, SubscriptionRequestInterface> = new Map(); private outstandingSubscriptions: Map<number, SubscriptionRequestInterface> =
new Map();
/** /**
* Our abort controller, used to close the connection * Our abort controller, used to close the connection
*/ */
private abort = new AbortController(); private abort = new AbortController();
@ -94,7 +108,7 @@ export class Urbit {
credentials: 'include', credentials: 'include',
accept: '*', accept: '*',
headers, headers,
signal: this.abort.signal signal: this.abort.signal,
}; };
} }
@ -102,15 +116,11 @@ export class Urbit {
* Constructs a new Urbit connection. * Constructs a new Urbit connection.
* *
* @param url The URL (with protocol and port) of the ship to be accessed. If * @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 * the airlock is running in a webpage served by the ship, this should just
* be the empty string. * be the empty string.
* @param code The access code for the ship at that address * @param code The access code for the ship at that address
*/ */
constructor( constructor(public url: string, public code?: string, public desk?: string) {
public url: string,
public code?: string,
public desk?: string
) {
if (isBrowser) { if (isBrowser) {
window.addEventListener('beforeunload', this.delete); window.addEventListener('beforeunload', this.delete);
} }
@ -119,18 +129,27 @@ export class Urbit {
/** /**
* All-in-one hook-me-up. * All-in-one hook-me-up.
* *
* Given a ship, url, and code, this returns an airlock connection * Given a ship, url, and code, this returns an airlock connection
* that is ready to go. It `|hi`s itself to create the channel, * that is ready to go. It `|hi`s itself to create the channel,
* then opens the channel via EventSource. * then opens the channel via EventSource.
* *
*/ */
static async authenticate({ ship, url, code, verbose = false }: AuthenticationInterface) { static async authenticate({
ship,
url,
code,
verbose = false,
}: AuthenticationInterface) {
const airlock = new Urbit(`http://${url}`, code); const airlock = new Urbit(`http://${url}`, code);
airlock.verbose = verbose; airlock.verbose = verbose;
airlock.ship = ship; airlock.ship = ship;
await airlock.connect(); await airlock.connect();
await airlock.poke({ app: 'hood', mark: 'helm-hi', json: 'opening airlock' }); await airlock.poke({
app: 'hood',
mark: 'helm-hi',
json: 'opening airlock',
});
await airlock.eventSource(); await airlock.eventSource();
return airlock; return airlock;
} }
@ -141,13 +160,18 @@ export class Urbit {
*/ */
async connect(): Promise<void> { async connect(): Promise<void> {
if (this.verbose) { if (this.verbose) {
console.log(`password=${this.code} `, isBrowser ? "Connecting in browser context at " + `${this.url}/~/login` : "Connecting from node context"); console.log(
`password=${this.code} `,
isBrowser
? 'Connecting in browser context at ' + `${this.url}/~/login`
: 'Connecting from node context'
);
} }
return fetch(`${this.url}/~/login`, { return fetch(`${this.url}/~/login`, {
method: 'post', method: 'post',
body: `password=${this.code}`, body: `password=${this.code}`,
credentials: 'include', credentials: 'include',
}).then(response => { }).then((response) => {
if (this.verbose) { if (this.verbose) {
console.log('Received authentication response', response); console.log('Received authentication response', response);
} }
@ -160,25 +184,28 @@ export class Urbit {
} }
}); });
} }
/** /**
* Initializes the SSE pipe for the appropriate channel. * Initializes the SSE pipe for the appropriate channel.
*/ */
async eventSource(): Promise<void> { async eventSource(): Promise<void> {
if(this.sseClientInitialized) { if (this.sseClientInitialized) {
return Promise.resolve(); return Promise.resolve();
} }
if(this.lastEventId === 0) { if (this.lastEventId === 0) {
// Can't receive events until the channel is open, // Can't receive events until the channel is open,
// so poke and open then // so poke and open then
await this.poke({ app: 'hood', mark: 'helm-hi', json: 'Opening API channel' }); await this.poke({
app: 'hood',
mark: 'helm-hi',
json: 'Opening API channel',
});
return; return;
} }
this.sseClientInitialized = true; this.sseClientInitialized = true;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const sseOptions: SSEOptions = { const sseOptions: SSEOptions = {
headers: {} headers: {},
}; };
if (isBrowser) { if (isBrowser) {
sseOptions.withCredentials = true; sseOptions.withCredentials = true;
@ -200,7 +227,7 @@ export class Urbit {
} else { } else {
const err = new Error('failed to open eventsource'); const err = new Error('failed to open eventsource');
reject(err); reject(err);
} }
}, },
onmessage: (event: EventSourceMessage) => { onmessage: (event: EventSourceMessage) => {
if (this.verbose) { if (this.verbose) {
@ -208,14 +235,17 @@ export class Urbit {
} }
if (!event.id) return; if (!event.id) return;
this.lastEventId = parseInt(event.id, 10); this.lastEventId = parseInt(event.id, 10);
if((this.lastEventId - this.lastAcknowledgedEventId) > 20) { if (this.lastEventId - this.lastAcknowledgedEventId > 20) {
this.ack(this.lastEventId); this.ack(this.lastEventId);
} }
if (event.data && JSON.parse(event.data)) { if (event.data && JSON.parse(event.data)) {
const data: any = JSON.parse(event.data); const data: any = JSON.parse(event.data);
if (data.response === 'poke' && this.outstandingPokes.has(data.id)) { if (
data.response === 'poke' &&
this.outstandingPokes.has(data.id)
) {
const funcs = this.outstandingPokes.get(data.id); const funcs = this.outstandingPokes.get(data.id);
if (data.hasOwnProperty('ok')) { if (data.hasOwnProperty('ok')) {
funcs.onSuccess(); funcs.onSuccess();
@ -226,22 +256,30 @@ export class Urbit {
console.error('Invalid poke response', data); console.error('Invalid poke response', data);
} }
this.outstandingPokes.delete(data.id); this.outstandingPokes.delete(data.id);
} else if (data.response === 'subscribe' } else if (
&& this.outstandingSubscriptions.has(data.id)) { data.response === 'subscribe' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id); const funcs = this.outstandingSubscriptions.get(data.id);
if (data.hasOwnProperty('err')) { if (data.hasOwnProperty('err')) {
console.error(data.err); console.error(data.err);
funcs.err(data.err, data.id); funcs.err(data.err, data.id);
this.outstandingSubscriptions.delete(data.id); this.outstandingSubscriptions.delete(data.id);
} }
} else if (data.response === 'diff' && this.outstandingSubscriptions.has(data.id)) { } else if (
data.response === 'diff' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id); const funcs = this.outstandingSubscriptions.get(data.id);
try { try {
funcs.event(data.json); funcs.event(data.json);
} catch (e) { } catch (e) {
console.error('Failed to call subscription event callback', e); console.error('Failed to call subscription event callback', e);
} }
} else if (data.response === 'quit' && this.outstandingSubscriptions.has(data.id)) { } else if (
data.response === 'quit' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id); const funcs = this.outstandingSubscriptions.get(data.id);
funcs.quit(data); funcs.quit(data);
this.outstandingSubscriptions.delete(data.id); this.outstandingSubscriptions.delete(data.id);
@ -253,7 +291,7 @@ export class Urbit {
}, },
onerror: (error) => { onerror: (error) => {
console.warn(error); console.warn(error);
if(!(error instanceof FatalError) && this.errorCount++ < 4) { if (this.errorCount++ < 4) {
this.onRetry && this.onRetry(); this.onRetry && this.onRetry();
return Math.pow(2, this.errorCount - 1) * 750; return Math.pow(2, this.errorCount - 1) * 750;
} }
@ -265,7 +303,7 @@ export class Urbit {
throw new Error('Ship unexpectedly closed the connection'); throw new Error('Ship unexpectedly closed the connection');
}, },
}); });
}) });
} }
/** /**
@ -301,7 +339,7 @@ export class Urbit {
this.lastAcknowledgedEventId = eventId; this.lastAcknowledgedEventId = eventId;
const message: Message = { const message: Message = {
action: 'ack', action: 'ack',
'event-id': eventId 'event-id': eventId,
}; };
await this.sendJSONtoChannel(message); await this.sendJSONtoChannel(message);
return eventId; return eventId;
@ -311,12 +349,12 @@ export class Urbit {
const response = await fetch(this.channelUrl, { const response = await fetch(this.channelUrl, {
...this.fetchOptions, ...this.fetchOptions,
method: 'PUT', method: 'PUT',
body: JSON.stringify(json) body: JSON.stringify(json),
}); });
if(!response.ok) { if (!response.ok) {
throw new Error('Failed to PUT channel'); throw new Error('Failed to PUT channel');
} }
if(!this.sseClientInitialized) { if (!this.sseClientInitialized) {
await this.eventSource(); await this.eventSource();
} }
} }
@ -335,23 +373,23 @@ export class Urbit {
let done = false; let done = false;
let id: number | null = null; let id: number | null = null;
const quit = () => { const quit = () => {
if(!done) { if (!done) {
reject('quit'); reject('quit');
} }
}; };
const event = (e: T) => { const event = (e: T) => {
if(!done) { if (!done) {
resolve(e); resolve(e);
this.unsubscribe(id); this.unsubscribe(id);
} }
} };
const request = { app, path, event, err: reject, quit }; const request = { app, path, event, err: reject, quit };
id = await this.subscribe(request); id = await this.subscribe(request);
if(timeout) { if (timeout) {
setTimeout(() => { setTimeout(() => {
if(!done) { if (!done) {
done = true; done = true;
reject('timeout'); reject('timeout');
this.unsubscribe(id); this.unsubscribe(id);
@ -369,18 +407,11 @@ export class Urbit {
* @param json The data to send * @param json The data to send
*/ */
async poke<T>(params: PokeInterface<T>): Promise<number> { async poke<T>(params: PokeInterface<T>): Promise<number> {
const { const { app, mark, json, ship, onSuccess, onError } = {
app, onSuccess: () => {},
mark, onError: () => {},
json,
ship,
onSuccess,
onError
} = {
onSuccess: () => { },
onError: () => { },
ship: this.ship, ship: this.ship,
...params ...params,
}; };
const message: Message = { const message: Message = {
id: this.getEventId(), id: this.getEventId(),
@ -388,7 +419,7 @@ export class Urbit {
ship, ship,
app, app,
mark, mark,
json json,
}; };
const [send, result] = await Promise.all([ const [send, result] = await Promise.all([
this.sendJSONtoChannel(message), this.sendJSONtoChannel(message),
@ -401,9 +432,9 @@ export class Urbit {
onError: (event) => { onError: (event) => {
onError(event); onError(event);
reject(event.err); reject(event.err);
} },
}); });
}) }),
]); ]);
return result; return result;
} }
@ -417,19 +448,12 @@ export class Urbit {
* @param handlers Handlers to deal with various events of the subscription * @param handlers Handlers to deal with various events of the subscription
*/ */
async subscribe(params: SubscriptionRequestInterface): Promise<number> { async subscribe(params: SubscriptionRequestInterface): Promise<number> {
const { const { app, path, ship, err, event, quit } = {
app, err: () => {},
path, event: () => {},
ship, quit: () => {},
err,
event,
quit
} = {
err: () => { },
event: () => { },
quit: () => { },
ship: this.ship, ship: this.ship,
...params ...params,
}; };
const message: Message = { const message: Message = {
@ -437,15 +461,19 @@ export class Urbit {
action: 'subscribe', action: 'subscribe',
ship, ship,
app, app,
path path,
}; };
this.outstandingSubscriptions.set(message.id, { this.outstandingSubscriptions.set(message.id, {
app, path, err, event, quit app,
path,
err,
event,
quit,
}); });
await this.sendJSONtoChannel(message); await this.sendJSONtoChannel(message);
return message.id; return message.id;
} }
@ -458,7 +486,7 @@ export class Urbit {
return this.sendJSONtoChannel({ return this.sendJSONtoChannel({
id: this.getEventId(), id: this.getEventId(),
action: 'unsubscribe', action: 'unsubscribe',
subscription subscription,
}).then(() => { }).then(() => {
this.outstandingSubscriptions.delete(subscription); this.outstandingSubscriptions.delete(subscription);
}); });
@ -469,9 +497,14 @@ export class Urbit {
*/ */
delete() { delete() {
if (isBrowser) { if (isBrowser) {
navigator.sendBeacon(this.channelUrl, JSON.stringify([{ navigator.sendBeacon(
action: 'delete' this.channelUrl,
}])); JSON.stringify([
{
action: 'delete',
},
])
);
} else { } else {
// TODO // TODO
// this.sendMessage('delete'); // this.sendMessage('delete');
@ -480,7 +513,7 @@ export class Urbit {
/** /**
* Scry into an gall agent at a path * Scry into an gall agent at a path
* *
* @typeParam T - Type of the scry result * @typeParam T - Type of the scry result
* *
* @remarks * @remarks
@ -496,14 +529,17 @@ export class Urbit {
*/ */
async scry<T = any>(params: Scry): Promise<T> { async scry<T = any>(params: Scry): Promise<T> {
const { app, path } = params; const { app, path } = params;
const response = await fetch(`${this.url}/~/scry/${app}${path}.json`, this.fetchOptions); const response = await fetch(
`${this.url}/~/scry/${app}${path}.json`,
this.fetchOptions
);
return await response.json(); return await response.json();
} }
/** /**
* Run a thread * Run a thread
* *
* *
* @param inputMark The mark of the data being sent * @param inputMark The mark of the data being sent
* @param outputMark The mark of the data being returned * @param outputMark The mark of the data being returned
* @param threadName The thread to run * @param threadName The thread to run
@ -511,15 +547,24 @@ export class Urbit {
* @returns The return value of the thread * @returns The return value of the thread
*/ */
async thread<R, T = any>(params: Thread<T>): Promise<R> { async thread<R, T = any>(params: Thread<T>): Promise<R> {
const { inputMark, outputMark, threadName, body, desk = this.desk } = params; const {
if(!desk) { inputMark,
throw new Error("Must supply desk to run thread from"); 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`, { const res = await fetch(
...this.fetchOptions, `${this.url}/spider/${desk}/${inputMark}/${threadName}/${outputMark}.json`,
method: 'POST', {
body: JSON.stringify(body) ...this.fetchOptions,
}); method: 'POST',
body: JSON.stringify(body),
}
);
return res.json(); return res.json();
} }
@ -536,6 +581,4 @@ export class Urbit {
} }
} }
export default Urbit; export default Urbit;

View File

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

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,3 +1,3 @@
export * from './types'; export * from './types';
import Urbit from './Urbit'; import { Urbit } from './Urbit';
export { Urbit as default }; export { Urbit as default, Urbit };

View File

@ -1,46 +1,17 @@
import * as http from 'http';
interface HttpResponse {
req: http.ClientRequest;
res: http.IncomingMessage;
data: string;
}
export function request(
url: string,
options: http.ClientRequestArgs,
body?: string
): Promise<HttpResponse> {
return new Promise<HttpResponse>((resolve, reject) => {
const req = http.request(url, options, res => {
let data = "";
res.on("data", chunk => {
data += chunk;
});
res.on("end", () => {
resolve({ req, res, data });
});
res.on("error", e => {
reject(e);
});
});
if (body) {
req.write(body);
}
req.end();
});
}
export function camelize(str: string) { export function camelize(str: string) {
return str return str
.replace(/\s(.)/g, function($1: string) { return $1.toUpperCase(); }) .replace(/\s(.)/g, function ($1: string) {
return $1.toUpperCase();
})
.replace(/\s/g, '') .replace(/\s/g, '')
.replace(/^(.)/, function($1: string) { return $1.toLowerCase(); }); .replace(/^(.)/, function ($1: string) {
return $1.toLowerCase();
});
} }
export function uncamelize(str: string, separator = '-') { export function uncamelize(str: string, separator = '-') {
// Replace all capital letters by separator followed by lowercase one // Replace all capital letters by separator followed by lowercase one
var str = str.replace(/[A-Z]/g, function (letter: string) { var str = str.replace(/[A-Z]/g, function (letter: string) {
return separator + letter.toLowerCase(); return separator + letter.toLowerCase();
}); });
return str.replace(new RegExp('^' + separator), ''); return str.replace(new RegExp('^' + separator), '');
@ -79,4 +50,4 @@ export function uid(): string {
str += _str + '.'; str += _str + '.';
} }
return str.slice(0, -1); return str.slice(0, -1);
} }

View File

@ -1,6 +1,4 @@
import Urbit from '../src'; import Urbit from '../src';
import { Readable } from 'streams';
import 'jest'; import 'jest';
function fakeSSE(messages = [], timeout = 0) { function fakeSSE(messages = [], timeout = 0) {
@ -54,7 +52,7 @@ const fakeFetch = (body) => () =>
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
process.on('unhandledRejection', () => { process.on('unhandledRejection', (error) => {
console.error(error); console.error(error);
}); });
@ -91,7 +89,7 @@ describe('Initialisation', () => {
) )
.mockImplementationOnce(() => .mockImplementationOnce(() =>
Promise.resolve({ ok: true, body: fakeSSE([], 100) }) Promise.resolve({ ok: true, body: fakeSSE([], 100) })
) );
airlock.onError = jest.fn(); airlock.onError = jest.fn();
try { try {
@ -163,7 +161,7 @@ describe('subscription', () => {
) )
.mockImplementationOnce(() => .mockImplementationOnce(() =>
Promise.resolve({ ok: false, body: fakeSSE([ack(1, true)]) }) Promise.resolve({ ok: false, body: fakeSSE([ack(1, true)]) })
) );
const params = { const params = {
app: 'app', app: 'app',

View File

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