Merge branch 'master' into next/groups

This commit is contained in:
Hunter Miller 2022-01-11 18:15:25 -06:00
commit 4ee680807f
94 changed files with 100749 additions and 43829 deletions

View File

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

View File

@ -4,4 +4,5 @@ bin/
.vscode/
.husky/
*.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",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"mock": "vite --mode mock",
@ -21,8 +22,8 @@
"@radix-ui/react-toggle": "^0.0.10",
"@tlon/sigil-js": "^1.4.4",
"@types/lodash": "^4.14.172",
"@urbit/api": "^1.4.0",
"@urbit/http-api": "^1.3.1",
"@urbit/api": "^2.1.0",
"@urbit/http-api": "^2.1.0",
"big-integer": "^1.6.48",
"classnames": "^2.3.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 React, { FC, useCallback, useState } from 'react';
import cn from 'classnames';
import { Vat } from '@urbit/api/hood';
import { Button, PillButton } from './Button';
import { Dialog, DialogClose, DialogContent, DialogTrigger } from './Dialog';
import { DocketHeader } from './DocketHeader';
@ -137,19 +136,19 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
</div>
</DocketHeader>
<div className="space-y-6">
{vat ? (
<>
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
<VatMeta vat={vat} />
</>
) : null}
{!treaty ? null : (
<>
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
<TreatyMeta treaty={treaty} />
</>
)}
</div>
{vat ? (
<>
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
<VatMeta vat={vat} />
</>
) : null}
{!treaty ? null : (
<>
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
<TreatyMeta treaty={treaty} />
</>
)}
</div>
</div>
);
};

View File

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

View File

@ -7,7 +7,13 @@ type ShipNameProps = {
export const ShipName = ({ name, ...props }: ShipNameProps) => {
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();
return (

View File

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

View File

@ -3,7 +3,7 @@ import classNames from 'classnames';
import clipboardCopy from 'clipboard-copy';
import React, { HTMLAttributes, useCallback, useState } from 'react';
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 { useVat } from '../state/kiln';
import { disableDefault, handleDropdownLink } from '../state/util';

View File

@ -1,6 +1,6 @@
import { pick, pickBy, partition } from 'lodash';
import React, { useCallback } from 'react';
import { kilnBump } from '@urbit/api/hood';
import { kilnBump } from '@urbit/api';
import { AppList } from '../../components/AppList';
import { Button } from '../../components/Button';
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 { Setting } from '../../components/Setting';
import { pokeOptimisticallyN } from '../../state/base';

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import {
putEntry as doPutEntry,
getDeskSettings,
DeskData
} from '@urbit/api/settings';
} from '@urbit/api';
import _ from 'lodash';
import {
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 _ from 'lodash';

View File

@ -73,7 +73,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,

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,

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import {
BigIntOrderedMap,
HarkPlace,
Timebox,
HarkStats,
harkBinToId,
makePatDa
} from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import _ from 'lodash';
import { compose } from 'lodash/fp';
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 { BaseState } from '../state/base';
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 { Cage } from '~/types/cage';
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 { SettingsState as State } from '~/logic/state/settings';
import { BaseState } from '../state/base';

View File

@ -1,12 +1,12 @@
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import { patp2dec } from 'urbit-ob';
import shallow from 'zustand/shallow';
import { Association, deSig, GraphNode, Graphs, FlatGraphs, resourceFromPath, ThreadGraphs, getGraph, getShallowChildren, setScreen } from '@urbit/api';
import {
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 { createState, createSubscription, reduceStateN, pokeOptimisticallyN } from './base';
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 _ from 'lodash';
import { clone } from '../lib/util';

View File

@ -1,5 +1,6 @@
import {
archive,
BigIntOrderedMap,
HarkBin,
markCountAsRead,
NotificationGraphConfig,
@ -15,7 +16,6 @@ import {
import { Poke } from '@urbit/http-api';
import { patp2dec } from 'urbit-ob';
import _ from 'lodash';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import api from '~/logic/api';
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 { useCallback, useEffect, useState } from 'react';
import {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
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 _ from 'lodash';
import { useResize } from '~/logic/lib/useResize';
import { LinkBlockItem } from './LinkBlockItem';
import { LinkBlockInput } from './LinkBlockInput';
import useLocalState from '~/logic/state/local';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt from 'big-integer';
import airlock from '~/logic/api';
import useHarkState, { selHarkGraph } from '~/logic/state/hark';

View File

@ -1,7 +1,7 @@
import { Box, Center, Col, Text } from '@tlon/indigo-react';
import React from 'react';
import useHarkState, { HarkState } from '~/logic/state/hark';
import { harkBinToId, HarkLid, Timebox } from '../../../../../npm/api/dist';
import { harkBinToId, HarkLid, Timebox } from '@urbit/api';
import { Notification } from './notification';
const unseenLid = { unseen: null };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 { Link, useHistory } from 'react-router-dom';
import { resourceFromPath } from '~/logic/lib/group';

View File

@ -1,12 +1,9 @@
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 React from 'react';
import { RouteComponentProps, useHistory } from 'react-router';
import { resourceFromPath } from '~/logic/lib/group';
import {
arrToString
} from '@urbit/api/lib/BigIntArrayOrderedMap';
import { keyEq, ThreadScroller } from '~/views/components/ThreadScroller';
import PostItem from './PostItem/PostItem';
import PostInput from './PostInput';

View File

@ -1,5 +1,5 @@
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 { getPermalinkForGraph } from '~/logic/lib/permalinks';
import { useCopy } from '~/logic/lib/useCopy';
@ -8,7 +8,6 @@ import { resourceFromPath } from '~/logic/lib/group';
import Author from '~/views/components/Author';
import { Dropdown } from '~/views/components/Dropdown';
import airlock from '~/logic/api';
import { removePosts } from '@urbit/api/graph';
interface PostHeaderProps {
post: Post;
association: Association;

View File

@ -6,11 +6,10 @@ import React, {
} from 'react';
import { resourceFromPath } from '~/logic/lib/group';
import { Loading } from '~/views/components/Loading';
import { arrToString } from '@urbit/api/lib/BigIntArrayOrderedMap';
import useGraphState from '~/logic/state/graph';
import PostFlatFeed from './PostFlatFeed';
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 { useGroupForAssoc } from '~/logic/state/group';

View File

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

View File

@ -1,5 +1,5 @@
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 _ from 'lodash';
import React from 'react';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import _ from 'lodash';
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';
@ -97,6 +95,10 @@ export const changePolicy = (
}
});
export const makeResource = (ship: string, name: string) => {
return { ship, name };
};
export const join = (
ship: string,
name: string,
@ -181,30 +183,32 @@ export const roleForShip = (
const roleShips = group?.tags?.role?.[role];
return roleShips && roleShips.has(ship) ? role : currRole;
}, undefined as RoleTags | undefined);
}
};
export const resourceFromPath = (path: Path): Resource => {
const [, , ship, name] = path.split('/');
return { ship, name };
}
export const makeResource = (ship: string, name: string) => {
return { ship, name };
}
};
export const isWriter = (group: Group, resource: string, ship: string) => {
const writers: Set<string> | undefined = _.get(
group,
['tags', 'graph', resource, 'writers'],
undefined
);
const graph = group.tags?.graph;
const writers: Set<string> | undefined = graph && (graph[resource] as any)?.writers;
const admins = group?.tags?.role?.admin ?? new Set();
if (_.isUndefined(writers)) {
if (typeof writers === 'undefined') {
return true;
} else {
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 = (
group: Group,
@ -218,13 +222,4 @@ export const isChannelAdmin = (
role === 'admin' ||
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';
export type RoleTags = typeof roleTags[number];
@ -173,5 +173,3 @@ export type GroupUpdate =
| GroupUpdateInitialGroup;
export type GroupAction = Omit<GroupUpdate, 'initialGroup' | 'initial'>;

View File

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

View File

@ -7,7 +7,8 @@ export * as groups from './groups';
export * from './hark';
export * as hark from './hark';
export * from './invite';
export * as invite from './invite';
// this conflicts with /groups/lib invite
// export * as invite from './invite';
export * from './metadata';
export * as metadata from './metadata';
export * from './settings';
@ -16,6 +17,7 @@ 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';

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import _ from "lodash";
import f from "lodash/fp";
import bigInt, { BigInteger } from "big-integer";
import { Resource } from "../groups/types";
@ -9,6 +7,36 @@ const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud
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.
*
@ -48,17 +76,11 @@ export function udToDec(ud: string): string {
}
export function decToUd(str: string): string {
return _.trimStart(
f.flow(
f.split(""),
f.reverse,
f.chunk(3),
f.map(f.flow(f.reverse, f.join(""))),
f.reverse,
f.join(".")
)(str),
"0."
);
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 {
@ -161,14 +183,11 @@ export function uxToHex(ux: string) {
}
export const hexToUx = (hex: string): string => {
const ux = f.flow(
f.dropWhile(y => y === '0'),
f.reverse,
f.chunk(4),
f.map(x => x.reverse().join('')),
f.reverse,
f.join('.')
)(hex.split('')) || '0';
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}`;
};
@ -210,21 +229,6 @@ export function stringToTa(str: string): string {
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 => ({
author: '',
contents: [],

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,52 @@
{
"name": "@urbit/api",
"version": "1.4.1",
"description": "",
"version": "2.1.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"
},
"main": "dist/index.js",
"types": "dist/index.d",
"type": "module",
"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": {
"test": "echo \"No test specified\" && exit 0",
"watch": "tsc -p tsconfig.json --watch",
"build": "tsc -p tsconfig.json",
"clean": "rm -rf dist/*",
"prepare": "npm run build"
"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.12.5",
"@types/lodash": "^4.14.168",
"@urbit/eslint-config": "^1.0.3",
"@babel/runtime": "^7.16.0",
"big-integer": "^1.6.48",
"core-js": "^3.19.1",
"immer": "^9.0.1",
"lodash": "^4.17.20",
"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",
"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"
}
}

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": {
"baseUrl": ".",
"outDir": "./dist",
"module": "ES2020",
"outDir": "./tmp",
"module": "ESNext",
"noImplicitAny": true,
"target": "ES2017",
"target": "ESNext",
"pretty": true,
"moduleResolution": "node",
"esModuleInterop": true,
@ -14,6 +14,11 @@
"strict": false,
"noErrorTruncation": true
},
"exclude": ["node_modules", "./dist/**/*", "@types"],
"exclude": [
"node_modules",
"./dist/**/*",
"./tmp/**/*",
"rollup.config.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>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Demo</title>
<script src="browser.js"></script>
<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;
}
@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>
</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.</code> Create a chat and send a message to see the events logged.</p>
<pre>window.airlock = await Urbit.authenticate({
<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('chat-view', '/primary', { event: console.log });</pre>
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>
<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';
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);
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;
}
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);
}
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);
instructions.innerText = instructions.innerText.replace(
'{window.location.origin}',
window.location.origin
);
async function blastOff() {
window.airlock = await Urbit.authenticate({
ship: 'zod',
url: 'localhost:8080',
code: 'lidlut-tabwed-pillex-ridrup',
verbose: true
});
window.airlock.subscribe('chat-view', '/primary', { event: console.log });
document.body.removeChild(document.getElementById('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>
</script>
</html>

View File

@ -23,7 +23,7 @@ module.exports = {
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
@ -137,7 +137,7 @@ module.exports = {
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "jsdom",
testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
@ -166,7 +166,7 @@ module.exports = {
// 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",
testURL: 'http://localhost',
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@urbit/http-api",
"version": "1.3.1",
"version": "2.1.0",
"license": "MIT",
"description": "Library to interact with an Urbit ship over HTTP",
"repository": {
@ -8,18 +8,21 @@
"url": "ssh://git@github.com/urbit/urbit.git",
"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",
"files": [
"dist",
"src"
"dist/**"
],
"scripts": {
"test": "jest",
"build": "tsc -p tsconfig.json",
"build": "npm run clean && rollup -c && npx tsc -p tsconfig.json",
"prepare": "npm run build",
"watch": "tsc -p tsconfig.json --watch",
"clean": "rm -rf dist/*"
"watch": "rollup -c -w",
"clean": "rm -rf dist/* types/*"
},
"prettier": {
"printWidth": 80,
@ -29,12 +32,12 @@
},
"author": "",
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@babel/preset-env": "^7.14.7",
"@babel/preset-typescript": "^7.12.1",
"@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",
@ -42,29 +45,22 @@
"@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0",
"babel-jest": "^27.0.6",
"babel-loader": "^8.2.1",
"clean-webpack-plugin": "^3.0.0",
"cross-fetch": "^3.1.4",
"event-target-polyfill": "0.0.3",
"fast-text-encoding": "^1.0.3",
"jest": "^27.0.6",
"onchange": "^7.1.0",
"tslib": "^2.0.3",
"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",
"webpack": "^5.4.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"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",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"stream-browserify": "^3.0.0",
"stream-http": "^3.1.1"
"core-js": "^3.19.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 { 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 { uncamelize, hexString } from './utils';
import {
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
@ -32,7 +45,7 @@ export class Urbit {
/**
* 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
@ -43,16 +56,17 @@ export class Urbit {
/**
* 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();
private outstandingSubscriptions: Map<number, SubscriptionRequestInterface> =
new Map();
/**
/**
* Our abort controller, used to close the connection
*/
private abort = new AbortController();
@ -94,7 +108,7 @@ export class Urbit {
credentials: 'include',
accept: '*',
headers,
signal: this.abort.signal
signal: this.abort.signal,
};
}
@ -102,15 +116,11 @@ export class Urbit {
* 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
* 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
) {
constructor(public url: string, public code?: string, public desk?: string) {
if (isBrowser) {
window.addEventListener('beforeunload', this.delete);
}
@ -119,18 +129,27 @@ export class Urbit {
/**
* 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) {
static async authenticate({
ship,
url,
code,
verbose = false,
}: AuthenticationInterface) {
const airlock = new Urbit(`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.poke({
app: 'hood',
mark: 'helm-hi',
json: 'opening airlock',
});
await airlock.eventSource();
return airlock;
}
@ -141,13 +160,18 @@ export class Urbit {
*/
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");
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 => {
}).then((response) => {
if (this.verbose) {
console.log('Received authentication response', response);
}
@ -160,25 +184,28 @@ export class Urbit {
}
});
}
/**
* Initializes the SSE pipe for the appropriate channel.
*/
async eventSource(): Promise<void> {
if(this.sseClientInitialized) {
if (this.sseClientInitialized) {
return Promise.resolve();
}
if(this.lastEventId === 0) {
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' });
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: {}
headers: {},
};
if (isBrowser) {
sseOptions.withCredentials = true;
@ -200,7 +227,7 @@ export class Urbit {
} else {
const err = new Error('failed to open eventsource');
reject(err);
}
}
},
onmessage: (event: EventSourceMessage) => {
if (this.verbose) {
@ -208,14 +235,17 @@ export class Urbit {
}
if (!event.id) return;
this.lastEventId = parseInt(event.id, 10);
if((this.lastEventId - this.lastAcknowledgedEventId) > 20) {
if (this.lastEventId - this.lastAcknowledgedEventId > 20) {
this.ack(this.lastEventId);
}
if (event.data && 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);
if (data.hasOwnProperty('ok')) {
funcs.onSuccess();
@ -226,22 +256,30 @@ export class Urbit {
console.error('Invalid poke response', data);
}
this.outstandingPokes.delete(data.id);
} else if (data.response === 'subscribe'
&& this.outstandingSubscriptions.has(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)) {
} else if (
data.response === 'diff' &&
this.outstandingSubscriptions.has(data.id)
) {
const funcs = this.outstandingSubscriptions.get(data.id);
try {
funcs.event(data.json);
} catch (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);
funcs.quit(data);
this.outstandingSubscriptions.delete(data.id);
@ -253,7 +291,7 @@ export class Urbit {
},
onerror: (error) => {
console.warn(error);
if(!(error instanceof FatalError) && this.errorCount++ < 4) {
if (this.errorCount++ < 4) {
this.onRetry && this.onRetry();
return Math.pow(2, this.errorCount - 1) * 750;
}
@ -265,7 +303,7 @@ export class Urbit {
throw new Error('Ship unexpectedly closed the connection');
},
});
})
});
}
/**
@ -301,7 +339,7 @@ export class Urbit {
this.lastAcknowledgedEventId = eventId;
const message: Message = {
action: 'ack',
'event-id': eventId
'event-id': eventId,
};
await this.sendJSONtoChannel(message);
return eventId;
@ -311,12 +349,12 @@ export class Urbit {
const response = await fetch(this.channelUrl, {
...this.fetchOptions,
method: 'PUT',
body: JSON.stringify(json)
body: JSON.stringify(json),
});
if(!response.ok) {
if (!response.ok) {
throw new Error('Failed to PUT channel');
}
if(!this.sseClientInitialized) {
if (!this.sseClientInitialized) {
await this.eventSource();
}
}
@ -335,23 +373,23 @@ export class Urbit {
let done = false;
let id: number | null = null;
const quit = () => {
if(!done) {
if (!done) {
reject('quit');
}
};
const event = (e: T) => {
if(!done) {
if (!done) {
resolve(e);
this.unsubscribe(id);
}
}
};
const request = { app, path, event, err: reject, quit };
id = await this.subscribe(request);
if(timeout) {
if (timeout) {
setTimeout(() => {
if(!done) {
if (!done) {
done = true;
reject('timeout');
this.unsubscribe(id);
@ -369,18 +407,11 @@ export class Urbit {
* @param json The data to send
*/
async poke<T>(params: PokeInterface<T>): Promise<number> {
const {
app,
mark,
json,
ship,
onSuccess,
onError
} = {
onSuccess: () => { },
onError: () => { },
const { app, mark, json, ship, onSuccess, onError } = {
onSuccess: () => {},
onError: () => {},
ship: this.ship,
...params
...params,
};
const message: Message = {
id: this.getEventId(),
@ -388,7 +419,7 @@ export class Urbit {
ship,
app,
mark,
json
json,
};
const [send, result] = await Promise.all([
this.sendJSONtoChannel(message),
@ -401,9 +432,9 @@ export class Urbit {
onError: (event) => {
onError(event);
reject(event.err);
}
},
});
})
}),
]);
return result;
}
@ -417,19 +448,12 @@ export class Urbit {
* @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: () => { },
const { app, path, ship, err, event, quit } = {
err: () => {},
event: () => {},
quit: () => {},
ship: this.ship,
...params
...params,
};
const message: Message = {
@ -437,15 +461,19 @@ export class Urbit {
action: 'subscribe',
ship,
app,
path
path,
};
this.outstandingSubscriptions.set(message.id, {
app, path, err, event, quit
app,
path,
err,
event,
quit,
});
await this.sendJSONtoChannel(message);
return message.id;
}
@ -458,7 +486,7 @@ export class Urbit {
return this.sendJSONtoChannel({
id: this.getEventId(),
action: 'unsubscribe',
subscription
subscription,
}).then(() => {
this.outstandingSubscriptions.delete(subscription);
});
@ -469,9 +497,14 @@ export class Urbit {
*/
delete() {
if (isBrowser) {
navigator.sendBeacon(this.channelUrl, JSON.stringify([{
action: 'delete'
}]));
navigator.sendBeacon(
this.channelUrl,
JSON.stringify([
{
action: 'delete',
},
])
);
} else {
// TODO
// this.sendMessage('delete');
@ -480,7 +513,7 @@ export class Urbit {
/**
* Scry into an gall agent at a path
*
*
* @typeParam T - Type of the scry result
*
* @remarks
@ -496,14 +529,17 @@ export class Urbit {
*/
async scry<T = any>(params: Scry): Promise<T> {
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();
}
/**
* 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
@ -511,15 +547,24 @@ export class Urbit {
* @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 {
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)
});
const res = await fetch(
`${this.url}/spider/${desk}/${inputMark}/${threadName}/${outputMark}.json`,
{
...this.fetchOptions,
method: 'POST',
body: JSON.stringify(body),
}
);
return res.json();
}
@ -536,6 +581,4 @@ export class 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';
import Urbit from './Urbit';
export { Urbit as default };
import { Urbit } from './Urbit';
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) {
return str
.replace(/\s(.)/g, function($1: string) { return $1.toUpperCase(); })
.replace(/\s(.)/g, function ($1: string) {
return $1.toUpperCase();
})
.replace(/\s/g, '')
.replace(/^(.)/, function($1: string) { return $1.toLowerCase(); });
.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) {
var str = str.replace(/[A-Z]/g, function (letter: string) {
return separator + letter.toLowerCase();
});
return str.replace(new RegExp('^' + separator), '');
@ -79,4 +50,4 @@ export function uid(): string {
str += _str + '.';
}
return str.slice(0, -1);
}
}

View File

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

View File

@ -1,23 +1,24 @@
{
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "@types"],
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "tmp"],
"compilerOptions": {
"outDir": "./dist",
"outDir": "./tmp",
"module": "ESNext",
"noImplicitAny": true,
"target": "ESNext",
"pretty": true,
"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/*", "*"]
"*": ["./node_modules/@types/*", "*"]
}
}
}