on master help sign in
Merge pull request #1363 from urbit/add-http-api-guide
739d29etinnus-napbus
content/docs/userspace/_index.md+4
@@ -12,6 +12,10 @@ Urbit's userspace is managed by Arvo's Gall vane.
12
12
13An overview of userspace in Urbit.
13An overview of userspace in Urbit.
14
14
15+## [HTTP API Guide](/docs/userspace/http-api-guide)
16+
17+Learn how to interact with a ship from a web client using the `@urbit/http-api` Javascript library.
18+
15## [Gall Guide](/docs/userspace/gall-guide/intro)
19## [Gall Guide](/docs/userspace/gall-guide/intro)
16
20
17Learn how to develop Gall agents.
21Learn how to develop Gall agents.
content/docs/userspace/http-api-guide.mdnew file+698
@@ -0,0 +1,698 @@
1++++
2+title = "HTTP API Guide"
3+weight = 4
4+template = "doc.html"
5++++
6+
7+[Eyre] is the web-server [vane] (kernel module) of [Arvo], an Urbit ship's
8+kernel and operating system. In this guide, we'll look at interacting with a
9+ship through Eyre's web API using the [@urbit/http-api] Javascript module.
10+
11+## Background
12+
13+Before we dig into the details of Eyre's API, we'll first give a quick run-down
14+of some concepts in Arvo. Eyre's API is a fairly thin overlay on some of Arvo's
15+internal systems, so there's some basic things to understand.
16+
17+### Clay
18+
19+[Clay] is the filesystem vane. It's typed, and it's revision-controlled in a
20+similar way to git. Clay is comprised of a number of [desk]s, which are a bit
21+like git repositories. Each app on your ship's home screen corresponds to a desk
22+in Clay. That desk contains the source code and resources for that app.
23+
24+#### Marks
25+
26+Most of Clay's workings aren't relevant to front-end development, but there's
27+one important concept to understand: [mark]s. Clay is a typed filesystem, and
28+marks are the filetypes. There's a mark for `.hoon` files, a mark for `.txt`
29+files, and so on. The mark specifies the hoon datatype for those files, and it
30+also specifies conversion methods between different marks. Marks aren't just
31+used for files saved in Clay, but also for data that goes to and from the web
32+through Eyre.
33+
34+When you send a [poke](#pokes) or run a [thread](#threads) through Eyre, it will
35+look at the mark specified (for example `graph-action-3`, `settings-event`, etc)
36+and use the corresponding mark file in the desk to convert the given JSON to
37+that datatype before passing it to the target [agent](#gall-agents) or thread.
38+The same conversion will happen in reverse for responses.
39+
40+Note that Eyre makes a best effort attempt to convert data to and from JSON. If
41+the marks in question do not contain appropriate JSON conversion functions, it
42+will fail. Not all [scry endpoints](#scry-endpoints), [subscription
43+paths](#subscriptions), pokes, etc, are intended to be used from a front-end, so
44+not all use marks which can be converted to and from JSON (the `noun` mark for
45+example). If documentation is available for the agent or thread in question, it
46+should note in some fashion whether it can take or produce JSON. The majority of
47+things you'll want to interact with through Eyre will work with JSON.
48+
49+### Gall agents
50+
51+An _agent_ is a userspace application managed by the [Gall] vane. A desk may
52+contain multiple agents that do different things. The Groups app, for example,
53+has `graph-store`, `group-store`, `contact-store`, and others in its desk.
54+Agents are the main thing you'll interact with through Eyre. They have a simple
55+interface with three main parts:
56+
57+| Interface | Description |
58+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59+| Pokes | One-off message to an agent. Pokes often represent actions, commands, requests, etc. |
60+| Subscriptions | An agent may have a number of different paths to which you may subscribe. If a subscription request is accepted, you'll continue to receive update/events the agent sends out on that path until you either cancel the subscription or are kicked by the agent. |
61+| Scries | Scries are one-off requests for data. Like subscriptions, they are organized by path. Scry requests will be fulfilled immediately. Scries are "read-only", they cannot alter the state of an agent. |
62+
63+#### Pokes
64+
65+Pokes are single, stand-alone messages to agents. Pokes are how you send data
66+and requests to agents. Agents will send back either a positive acknowledgement
67+(ack) or a negative acknowledgement (nack). The agent can't send actual data
68+back in the acks. If they have any response to give back, it will be sent out to
69+subscribers on a subscription path instead.
70+
71+The pokes an agent accepts will be defined in the `on-poke` section of its
72+source in the desk's `/app` directory, and typically in its type definition file
73+in the `/sur` directory too. If the agent has documentation, the data structures
74+and marks of its pokes should be listed there.
75+
76+`http-api` includes a `poke` function which allows you to perform pokes through
77+Eyre, and is [detailed below](#poke).
78+
79+#### Subscriptions
80+
81+Agents define subscription paths which you can subscribe to through Eyre. A path
82+might be simple and fixed like `/foo/bar`, or it might have variable elements
83+where you can specify a date, ship, or what have you. Each agent will define its
84+subscription paths in the `on-watch` section of its source, and if it has
85+documentation the paths should also be listed there.
86+
87+You can subscribe by sending a watch request to the agent with the desired path
88+specifed. The agent will apply some logic (such as checking permissions) to the
89+request and then ack or nack it. If acked, you'll be subscribed and begin
90+receiving any updates the agent sends out on that path. What you'll receive on a
91+given path depends entirely on the agent.
92+
93+Agents can kick subscribers, and you can also unsubscribe at any time.
94+
95+`http-api` includes a `subscribe` function which allows you to perform pokes through
96+Eyre, and is [detailed below](#subscribe).
97+
98+#### Scry Endpoints
99+
100+Pokes and subscriptions can modify the state of the agent. A third kind of
101+interaction called a _scry_ does not - it simply retrieves data from the agent
102+without any side-effects. Agents can define _scry endpoints_ which (like
103+subscriptions) are paths. A scry to one of these endpoints will retrieve some
104+data (as determined by the agent's design) from the agent's state. Like
105+subscription paths, scry paths can be simple like `/foo/bar` or contain variable
106+elements. Unlike subscriptions, a scry is a one-off request and the data will
107+come back immediately.
108+
109+Scry endpoints are defined in the `on-peek` section of an agent's source, and
110+will likely be listed in the agent's documentation if it has any. Scry endpoints
111+will often be written with a leading letter like `/x/foo/bar`. All scries
112+through Eyre are `x` scries, so that letter (called a `care`) needn't be
113+specified.
114+
115+`http-api` includes a `scry` function which allows you to perform scries through
116+Eyre, and is [detailed below](#scry).
117+
118+### Threads
119+
120+A thread is a monadic function in Arvo that takes arguments and produces a
121+result. Threads are conceptually similar to Javascript promises - they can
122+perform a series of asynchronous I/O operations and may succeed or fail. Threads
123+are often used to handle complex I/O operations for agents. Threads live in the
124+`/ted` directory of a desk.
125+
126+`http-api` includes a `thread` function which allows you to run threads through
127+Eyre, and is [detailed below](#thread).
128+
129+## `http-api` basics
130+
131+The `http-api` module provides convenient functions to interact with a ship
132+through Eyre. In this section we'll discuss the basics of how it works.
133+
134+### Importing the module
135+
136+The `http-api` module is available in npm as `@urbit/http-api`, and can be
137+installed with:
138+
139+```
140+npm i @urbit/http-api
141+```
142+
143+Once installed, you can import it into your app with:
144+
145+```
146+import Urbit from '@urbit/http-api';
147+```
148+
149+Note that the examples in this guide are simple HTML documents with vanilla
150+Javascript in `<script>` tags, so they use [unpkg.com](https://unpkg.com) to
151+import `@urbit/http-api`. This is not typical, and is just done here for
152+purposes of simplicity.
153+
154+### `Urbit`
155+
156+All functionality is contained within the `Urbit` class. There are two ways to
157+instantiate it, depending on whether your web app is served directly from the
158+ship or whether it's served externally. The reason for the difference is that
159+you require a session cookie to talk to the ship. If your app is served from the
160+ship, the user will already have a cookie. If it's not, they'll need to
161+authenticate, which is [detailed separately below](#authentication).
162+
163+In the case of a front-end served from the ship, the `Urbit` class contains a `constructor` which takes three arguments:
164+
165+| Argument | Type | Description | Example |
166+| -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
167+| `url` | `string` | The host of the ship without the protocol. This is mandatory but would typically be left blank as requests will still work if they're root-relative paths. | `"example.com"`, `"localhost:8080"`, `""` |
168+| `code` | `string` | The web login code of the ship. You'll typically use this constructor when it's served directly from the ship and therefore you'll already have a session cookie, so this field is optional and is typically omitted or left empty. | `""`, `"lidlut-tabwed-pillex-ridrup"` |
169+| `desk` | `string` | The desk on which you want to run threads. This field is optional and is only used if you want to run threads, no other function uses it. If you're not running threads, you can omit it or leave it empty. | `"landscape"`, `""` |
170+
171+To create an `Urbit` instance, you can simply do:
172+
173+```javascript
174+const api = new Urbit("");
175+```
176+
177+If you want to specify a desk, you can do:
178+
179+```javascript
180+const api = new Urbit("", "", "landscape");
181+```
182+
183+### `/session.js`
184+
185+Most functions of the `Urbit` class need to know the ship name or they will
186+fail. This is given explicitly with the external authentication method [detailed
187+below](#authentication), but not when using the `Urbit` constructor in a web app
188+served directly from the ship. Rather than having the user manually enter it,
189+Urbit ships serve a JS library at `/session.js` that contains the following:
190+
191+```javascript
192+window.ship = "zod";
193+```
194+
195+`"zod"` will be replaced with the actual name of the ship in question. You can
196+include this file like:
197+
198+```
199+<script src="/session.js"></script>
200+```
201+
202+Then you need to set the `ship` field in the `Urbit` object. You would typically
203+do it immediately after instantiating it:
204+
205+```javascript
206+const api = new Urbit("");
207+api.ship = window.ship;
208+```
209+
210+### Channels
211+
212+With the exception of scries and threads, all communication with Eyre happens
213+through its channel system.
214+
215+The `Urbit` object will generate a random channel ID that looks like
216+`1646295453-e1bdfd`, and use a path of `/~/channel/1646295453-e1bdfd` for
217+channel communications. Pokes will and subscription requests will be sent to
218+that channel. Responses and subscription updates will be sent out to you on that
219+channel too.
220+
221+Eyre sends out updates and responses on an [SSE] (Server Sent Event) stream for
222+the channel. The `Urbit` object handles this internally with an `eventSource`
223+object, but you won't deal with it directly. Eyre requires all events it sends
224+out be acknowledged by the client, and will eventually close the channel if
225+enough unacknowledged events accumulate. The `Urbit` object handles event
226+acknowledgement automatically, so you don't need to worry about this.
227+
228+Eyre automatically creates a channel when a poke or subscription request is
229+first sent to `/~/channel/[a new channel ID]`. If your web app is served
230+external to the ship and you use the `authenticate` function [described
231+below](#authentication), the function will automatically send a poke and open
232+the channel. If your web app is served directly from the ship and you use
233+`Urbit` class constructor, it won't open the channel right away. Instead, the
234+channel will be opened whenever you first send a poke or subscription request.
235+
236+### Connection state
237+
238+`Urbit` class includes three optional callbacks that fire when the SSE
239+connection state changes:
240+
241+| Callback | Description |
242+| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
243+| `Urbit.onOpen` | Called when an SSE channel connection is successfully established. |
244+| `Urbit.onRetry` | This is called whenever a reconnection attempt is made due to an interruption, for example if there are network problems. |
245+| `Urbit.onError` | This is called when there is an unrecoverable error, for example after enough reconnection attemps have failed. |
246+
247+As mentioned in the previous section, typically a channel will be opened and an
248+SSE connection established after you first poke the ship or make a subscription
249+request. If successful, whatever function you provided to `onOpen` will be
250+called. If at some point the connection is interrupted, a reconnection attempt will be made three times:
251+
252+1. Instantly.
253+2. 750ms after the first.
254+3. 3000ms after the second.
255+
256+Each attempt will call the function you provided to `onRetry`, if any. If all
257+three reconnection attempts failed, or if a fatal error occurred, the function
258+you provided `onError` will be called with an `Error` object containing an error
259+message as its argument.
260+
261+How you use these (if you do at all) is up to you and depends on the nature of
262+your app. If you want to try reconnecting when `onError` fires, note that Eyre
263+will delete a channel if it's had no messages from the client in the last 12
264+hours. The timeout is reset whenever it receives a message, including the acks
265+that are automatically sent by the `Urbit` object in response to subscription
266+updates.
267+
268+If you don't want to account for the possibility of the channel having been
269+deleted in your app's logic, you can also just call the [`reset()`](#reset)
270+function before you try reconnecting and consequently open a brand new channel.
271+
272+### Authentication
273+
274+**If your front-end is served directly from the Urbit ship, this step can be
275+skipped.** If your web app is served externally to the ship, you must
276+authenticate and obtain a session cookie before commencing communications with
277+the ship.
278+
279+The `Urbit` class in `http-api` includes an `authenticate` function which does
280+the following:
281+
282+1. Login to the user's ship with their `code` and obtain a session cookie.
283+2. Generate a random channel ID for the connection.
284+3. Poke the user's ship and print "opening airlock" in the dojo to initialize
285+ the channel.
286+
287+The `authenticate` function takes four arguments in an object: `ship`, `url`,
288+`code` and `verbose`:
289+
290+| Argument | Type | Description | Example |
291+| --------- | --------- | -------------------------------------------------------------------------------------- | --------------------------------- |
292+| `ship` | `string` | The ship ID (`@p`) without the leading `~`. | `"sampel-palnet"` or `"zod"` |
293+| `url` | `string` | The base URL for the ship without the protocol. | `localhost:8080` or `example.com` |
294+| `code` | `string` | The user's web login code. | `"lidlut-tabwed-pillex-ridrup"` |
295+| `verbose` | `boolean` | Whether to log details to the console. This field is optional and defaults to `false`. | `true` |
296+
297+This function returns a promise that if successful, produces an `Urbit` object
298+which can then be used for communications with the ship.
299+
300+**Note:** An Urbit ship will deny CORS requests from external URLs by default.
301+In order to allow CORS for an external web app, the user will need to run the
302+following in the dojo (replacing `https://example.com` with the address in
303+question):
304+
305+```
306+|cors-approve 'https://example.com'
307+```
308+
309+#### Example
310+
311+Here's a simple example that will run `authenticate` for a fakezod when the
312+_Connect_ button is pressed:
313+
314+```
315+<html>
316+ <head>
317+ <script src="https://unpkg.com/@urbit/http-api"></script>
318+ </head>
319+ <body>
320+ <button id="start" type="button" onClick="connect()" >Connect</button>
321+ </body>
322+ <script>
323+ async function connect() {
324+ window.api = await UrbitHttpApi.Urbit.authenticate({
325+ ship: "zod",
326+ url: "localhost:8080",
327+ code: "lidlut-tabwed-pillex-ridrup",
328+ verbose: true
329+ });
330+ document.body.innerHTML = "Connected!";
331+ };
332+ </script>
333+</html>
334+```
335+
336+`window.api` can then be used for further communications.
337+
338+## Functions
339+
340+The various functions you can use in the `Urbit` object are described below.
341+
342+### Poke
343+
344+For poking a ship, the `Urbit` class in `http-api` includes a `poke` function.
345+The `poke` function takes six arguments in a object:
346+
347+| Argument | Type | Description | Example |
348+| ----------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
349+| `app` | `string` | The Gall agent to poke. | `"graph-store"` |
350+| `mark` | `string` | The mark of the data to poke the agent with. | `"graph-update-3"` |
351+| `json` | any JSON | The data to poke the agent with. | `{foo: "bar", baz: 42}` |
352+| `ship` | `string` | The Urbit ID (`@p`) of the ship without the `~`. This may be ommitted if it's already been set for the whole `Urbit` object. | `"sampel-palnet"` |
353+| `onSuccess` | A function. | This is called if the poke succeeded (the ship ack'd the poke). | `someFunction` |
354+| `onError` | A function. | This is called if the poke failed (the ship nack'd the poke). | `anotherFunction` |
355+
356+#### Example
357+
358+This example lets you "hi" your ship with a given message, which will be printed
359+in the terminal. It pokes the `hood` agent (the system agent) with a `helm-hi`
360+mark. The `json` argument just contains the message in a string. If for some
361+reason the `hood` agent rejects the poke, "Poke failed!" will be displayed on
362+the page.
363+
364+```
365+<html>
366+ <head>
367+ <script src="https://unpkg.com/@urbit/http-api"></script>
368+ <script src="/session.js"></script>
369+ </head>
370+ <body>
371+ <input id="msg" type="text" placeholder="Message for ship" />
372+ <button id="submit" type="button" onClick="doPoke()" >Submit</button>
373+ <p id="err"></p>
374+ </body>
375+ <script>
376+ document.getElementById("msg")
377+ .addEventListener("keyup", function(event) {
378+ if (event.keyCode === 13) {
379+ document.getElementById("submit").click();
380+ }
381+ })
382+ const api = new UrbitHttpApi.Urbit("");
383+ api.ship = window.ship;
384+ function doPoke() {
385+ const msg = document.getElementById("msg").value;
386+ api.poke({
387+ app: "hood",
388+ mark: "helm-hi",
389+ json: msg,
390+ onSuccess: success,
391+ onError: error
392+ });
393+ }
394+ function success() {
395+ document.getElementById("msg").value = "";
396+ document.getElementById("err").innerHTML = "";
397+ }
398+ function error() {
399+ document.getElementById("msg").value = "";
400+ document.getElementById("err").innerHTML = "Poke failed!";
401+ }
402+ </script>
403+</html>
404+```
405+
406+### Subscribe and Unsubscribe {#subscribe}
407+
408+For subscribing to a particular path in an agent, the `Urbit` class in `http-api` includes a `subscribe` function.
409+The `subscribe` function takes six arguments in a object:
410+
411+| Argument | Type | Description | Example |
412+| -------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
413+| `app` | `string` | The Gall agent to which you'll subscribe. | `"graph-store"` |
414+| `path` | `string` | The subscription path. | `"/updates"` |
415+| `ship` | `string` | The Urbit ID (`@p`) of the ship without the `~`. This may be ommitted if it's already been set for the whole `Urbit` object. | `"sampel-palnet"` |
416+| `err` | A function. | This is called if the subscription request fails. | `someFunction` |
417+| `event` | A function. | This is the function to handle each update you receive for this subscription. The function's argument is the update's JSON data. | `anotherFunction` |
418+| `quit` | A function. | This is called if you are kicked from the subscription. | `yetAnotherFunction` |
419+
420+The `subscribe` function returns a subscription ID, which is just a number. This
421+ID can be used to unsubscribe down the line.
422+
423+If the subscription request is successful, you'll continue to receive updates
424+until you either unsubscribe or are kicked by the agent. You may subscribe to
425+multiple different agents and subscription paths by calling the `subscribe`
426+function for each one.
427+
428+If you wish to unsubscribe from a particular subscription, the `Urbit` class in
429+`http-api` includes an `unsubscribe` function. This function just takes a single
430+argument: the subscription ID number of an existing subscription. Once
431+unsubscribed, you'll stop receiving updates for the specified subscription.
432+
433+#### Example
434+
435+This example will, upon clicking the "Subscribe" button, subscribe to the
436+`/updates` path of the `graph-store` agent, and print the raw JSON of each
437+update it receives. You can test it by doing something like posting a message in
438+a chat. You can also unsubscribe by clicking the "Unsubscribe" button.
439+
440+```
441+<html>
442+ <head>
443+ <script src="https://unpkg.com/@urbit/http-api"></script>
444+ <script src="/session.js"></script>
445+ </head>
446+ <body>
447+ <button id="toggle" type="button" onClick="doSub()" >Subscribe</button>
448+ <pre id="event"></pre>
449+ </body>
450+ <script>
451+ const api = new UrbitHttpApi.Urbit("");
452+ api.ship = window.ship;
453+ const toggle = document.getElementById("toggle");
454+ const event = document.getElementById("event");
455+ function doSub() {
456+ window.id = api.subscribe({
457+ app: "graph-store",
458+ path: "/updates",
459+ event: printEvent,
460+ quit: doSub,
461+ err: subFail
462+ });
463+ toggle.innerHTML = "Unsubscribe";
464+ toggle.onclick = doUnsub;
465+ event.innerHTML = "Awaiting event";
466+ };
467+ function doUnsub() {
468+ api.unsubscribe(window.id);
469+ toggle.innerHTML = "Subscribe";
470+ toggle.onclick = doSub;
471+ event.innerHTML = "Subscription closed";
472+ };
473+ function printEvent(update) {
474+ event.innerHTML = JSON.stringify(update, null, 2);
475+ };
476+ function subFail() {
477+ event.innerHTML = "Subscription failed!";
478+ };
479+ </script>
480+</html>
481+```
482+
483+### Subscribe Once
484+
485+The `subscribeOnce` function in the `Urbit` class of `http-api` is a variation
486+on the ordinary [`subscribe`](#subscribe) function. Rather than keeping the
487+subscription going and receiving an arbitrary number of updates, instead it
488+waits to receive a single update and then closes the subscription. This is
489+useful if, for example, you send a poke and just want a response to that one
490+poke.
491+
492+The `subscribeOnce` function also takes an optional `timeout` argument, which
493+specifies the number of milliseconds to wait for an update before closing the
494+subscription. If omitted, `subscribeOnce` will wait indefinitely.
495+
496+`subscribeOnce` takes three arguments (these can't be in an object like most
497+other `Urbit` functions):
498+
499+| Argument | Type | Description | Example |
500+| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | --------------- |
501+| `app` | `string` | The Gall agent to which you'll subscribe. | `"graph-store"` |
502+| `path` | `string` | The subscription path. | `"/updates"` |
503+| `timeout` | `number` | The number of milliseconds to wait for an update before closing the subscription. If omitted, it will wait indefinitely. | `5000` |
504+
505+`subscribeOnce` returns a Promise. If successful, the Promise produces the JSON
506+data of the update it received. If it failed due to either timing out or getting
507+kicked from the subscription, it will return an error message of either
508+`"timeout"` or `"quit"`.
509+
510+#### Example
511+
512+Here's a variation on the ordinary [subscribe](#subscribe) function. Rather than
513+printing every `graph-store` update it receives, it will instead just print the
514+first one it receives and close the subscription. Additionally, it sets a five
515+second timeout, and prints an error message if it times out.
516+
517+```
518+<html>
519+ <head>
520+ <script src="https://unpkg.com/@urbit/http-api"></script>
521+ <script src="/session.js"></script>
522+ </head>
523+ <body>
524+ <button type="button" onClick="doSub()" >Subscribe Once</button>
525+ <pre id="event"></pre>
526+ </body>
527+ <script>
528+ const api = new UrbitHttpApi.Urbit("");
529+ api.ship = window.ship;
530+ const event = document.getElementById("event");
531+ function doSub() {
532+ event.innerHTML = "";
533+ api.subscribeOnce("graph-store","/updates",5000)
534+ .then(printEvent, noEvent);
535+ };
536+ function printEvent(update) {
537+ event.innerHTML = JSON.stringify(update, null, 2);
538+ };
539+ function noEvent(error) {
540+ event.innerHTML = error;
541+ };
542+ </script>
543+</html>
544+```
545+
546+### Scries
547+
548+To scry agents on the ship, the `Urbit` class in `http-api` includes a `scry` function.
549+The `scry` function takes two arguments in a object:
550+
551+| Argument | Type | Description | Example |
552+| -------- | -------- | ---------------------------------- | --------------- |
553+| `app` | `string` | The agent to scry. | `"graph-store"` |
554+| `path` | `string` | The path to scry, sans the `care`. | `"/keys"` |
555+
556+The `scry` function returns a promise that, if successful, contains the
557+requested data as JSON. If the scry failed, for example due to a non-existent
558+scry endpoint, connection problem, or mark conversion failure, the promise will
559+fail.
560+
561+#### Example
562+
563+Upon pressing the "Scry Graphs" button, this example will scry the `graph-store`
564+agent's `/keys` endpoint for the list of graphs, and print the resulting JSON
565+data.
566+
567+```
568+<html>
569+ <head>
570+ <script src="https://unpkg.com/@urbit/http-api"></script>
571+ <script src="/session.js"></script>
572+ </head>
573+ <body>
574+ <button id="scry" type="button" onClick="doScry()" >Scry Graphs</button>
575+ <pre id="result"></pre>
576+ </body>
577+ <script>
578+ const api = new UrbitHttpApi.Urbit("");
579+ api.ship = window.ship;
580+ async function doScry() {
581+ var groups = await api.scry({app: "graph-store", path: "/keys"});
582+ document.getElementById("result")
583+ .innerHTML = JSON.stringify(groups, null, 2);
584+ }
585+ </script>
586+</html>
587+```
588+
589+### Thread
590+
591+To run a thread, the `Urbit` class in `http-api` includes a `thread` function.
592+The `thread` function takes five arguments in an object:
593+
594+| Argument | Type | Description | Example |
595+| ------------ | -------- | ------------------------------------------------------------------------------------------------------------- | ----------------------- |
596+| `inputMark` | `string` | The mark to convert your JSON data to before giving it to the thread as its argument. | `"graph-view-action"` |
597+| `outputMark` | `string` | The result of the thread should be converted to this mark before being converted to JSON and returned to you. | `"tang"` |
598+| `threadName` | `string` | The name of the thread to run. | `"graph-eval"` |
599+| `body` | any JSON | The data to give to the thread as its argument. | `{foo: "bar", baz: 42}` |
600+| `desk` | `string` | The desk in which the thread resides. This may be ommitted if previously set for the whole `Urbit` object. | `"landscape"` |
601+
602+The `thread` function will produce a promise that, if successful, contains the
603+JSON result of the thread. If the thread failed, a connection error occurred, or
604+mark conversion failed, the promise will fail.
605+
606+#### Example
607+
608+This example takes a hoon expression (such as `(add 1 1)`), evalutes it with the
609+`graph-eval` thread in the `landscape` desk, and prints the result.
610+
611+```
612+<html>
613+ <head>
614+ <script src="https://unpkg.com/@urbit/http-api"></script>
615+ <script src="/session.js"></script>
616+ </head>
617+ <body>
618+ <input id="hoon" type="text" placeholder="Hoon to evaluate" />
619+ <button id="submit" type="button" onClick="runThread()" >Submit</button>
620+ <pre id="expr"></pre>
621+ <pre id="result"></pre>
622+ </body>
623+ <script>
624+ document.getElementById("hoon")
625+ .addEventListener("keyup", function(event) {
626+ if (event.keyCode === 13) {
627+ document.getElementById("submit").click();
628+ }
629+ });
630+ const hoon = document.getElementById("hoon");
631+ const expr = document.getElementById("expr");
632+ const result = document.getElementById("result");
633+ const desk = "landscape";
634+ const api = new UrbitHttpApi.Urbit("", "", desk);
635+ api.ship = window.ship;
636+ async function runThread() {
637+ const threadResult = await api.thread({
638+ inputMark: "graph-view-action",
639+ outputMark: "tang",
640+ threadName: "graph-eval",
641+ body: {eval: hoon.value}
642+ });
643+ result.innerHTML = threadResult[0].join("\n");
644+ expr.innerHTML = hoon.value;
645+ hoon.value = "";
646+ };
647+ </script>
648+</html>
649+```
650+
651+### Delete Channel
652+
653+Rather than just closing individual subscriptions, the entire channel can be
654+closed with the `delete` function in the `Urbit` class of `http-api`. The
655+function takes no arguments, and can be called like `api.cancel()` (replacing
656+`api` with whatever you've named the `Urbit` object you previously instantiated).
657+When a channel is closed, all subscriptions will be cancelled and all pending
658+updates will be discarded.
659+
660+### Reset
661+
662+An existing instance of `http-api`'s `Urbit` class can be reset with its
663+`reset` function. This function takes no arguments, and can be called like
664+`api.reset()` (replacing `api` with whatever you've named the `Urbit` object you
665+previously instantiated). When called, the channel will be closed and all
666+subscriptions cancelled. Additionally, all outstanding outbound pokes will be
667+discarded and a fresh channel ID will be generated.
668+
669+## Further reading
670+
671+- [Eyre External API Reference][eyre-ext-ref] - Lower-level documentation of
672+ Eyre's external API.
673+- [Eyre Guide][eyre-guide] - Lower-level examples of using Eyre's external API
674+ with `curl`.
675+- [`http-api` on Github][http-api-src] - The source code for `@urbit/http-api`.
676+
677+## Examples
678+
679+Here are some examples which make use of `@urbit/http-api` that might be useful
680+as a reference:
681+
682+- [Bitcoin Wallet](https://github.com/urbit/urbit/tree/master/pkg/btc-wallet)
683+- [Web Terminal](https://github.com/urbit/urbit/tree/master/pkg/interface/webterm)
684+- [UrChatFM](https://github.com/urbit/urbit-webrtc/tree/master/urchatfm)
685+
686+[eyre-ext-ref]: /docs/arvo/eyre/external-api-ref
687+[eyre-guide]: /docs/arvo/eyre/guide
688+[http-api-src]: https://github.com/urbit/urbit/tree/master/pkg/npm/http-api
689+[eyre]: /docs/glossary/eyre
690+[vane]: /docs/glossary/vane
691+[arvo]: /docs/glossary/arvo
692+[hoon]: /docs/glossary/hoon
693+[gall]: /docs/glossary/gall
694+[clay]: /docs/glossary/clay
695+[desk]: /docs/glossary/desk
696+[mark]: /docs/glossary/mark
697+[sse]: https://html.spec.whatwg.org/#server-sent-events
698+[@urbit/http-api]: https://github.com/urbit/urbit/tree/master/pkg/npm/http-api