Skip to content

Commit

Permalink
Build Guide: Connecting Everything Together (#527)
Browse files Browse the repository at this point in the history
* feat: stub page for front end overview, add to nav

* feat: Explanation and example for connecting from vanilla JS

* edit: rename connection function

* feat: TOC for connecting everything together section

* feat: connecting everything together overview page

* edit: add bibliograpy to connecting page

* edit: mention signals in connecting page

* fix: turn conn getter into IIFE for compatibility

* edit: rename to Connecting the parts

* edit: actually talk about hc-launch

* edit: tiny text edit

* fix: broken fragment ID
  • Loading branch information
pdaoust authored Feb 11, 2025
1 parent d3f0ab5 commit ee6de8b
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 16 deletions.
3 changes: 3 additions & 0 deletions src/pages/_data/navigation/mainNav.json5
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
{ title: "Entries", url: "/build/entries/" },
{ title: "Links, Paths, and Anchors", url: "/build/links-paths-and-anchors/" },
]},
{ title: "Connecting the Parts", url: "/build/connecting-the-parts/", children: [
{ title: "Front End", url: "/build/connecting-a-front-end/" },
]},
]
},
{ title: "Resources", url: "/resources/", children: [
Expand Down
2 changes: 1 addition & 1 deletion src/pages/build/application-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The hApp can specify two provisioning strategies for its DNAs:

A hApp can optionally include a web-based UI that supporting Holochain runtimes <!-- TODO: link --> can serve to the user.

!!! info A hApp always runs locally
!!! info A hApp always runs locally {#local}

The big difference with peer-to-peer stacks like Holochain is that **all the code** --- both the back end and the front end --- **runs on the devices of the participants themselves**.

Expand Down
24 changes: 12 additions & 12 deletions src/pages/build/callbacks-and-lifecycle-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,13 @@ use foo_integrity::LinkTypes;
use hdk::prelude::*;

// We're creating this type for both remote signals to other peers and local
// signals to the UI.
// signals to the UI. Your app might have different kinds of signals for each,
// so you're free to define separate types for local vs remote.
// We recommend making your signal type an enum, so your hApp can define
// different kinds of signals.
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
// It's helpful to match the way Holochain serializes its own enums.
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
enum Signal {
Heartbeat(AgentPubKey),
}
Expand Down Expand Up @@ -227,7 +231,8 @@ use movie_integrity::{EntryTypes, Movie, MovieLoan, UnitEntryTypes};
use hdk::prelude::*;

#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum RemoteSignal {
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum Signal {
MovieLoanHasBeenCreatedForYou(ActionHash),
}

Expand All @@ -238,25 +243,20 @@ pub fn post_commit(actions: Vec<SignedActionHashed>) {
if let Action::Create(_) = action.action() {
if let Ok(movie_loan) = get_movie_loan(action.action_address().clone()) {
send_remote_signal(
RemoteSignal::MovieLoanHasBeenCreatedForYou(action.action_address().clone()),
Signal::MovieLoanHasBeenCreatedForYou(action.action_address().clone()),
vec![movie_loan.lent_to]
).ok(); // suppress warning about unhandled `Result`
}
}
}
}

#[derive(Serialize, Deserialize, Debug)]
enum LocalSignal {
NewMovieLoan(MovieLoan),
}

#[hdk_extern]
pub fn recv_remote_signal(payload: RemoteSignal) -> ExternResult<()> {
if let RemoteSignal::MovieLoanHasBeenCreatedForYou(action_hash) = payload {
pub fn recv_remote_signal(payload: Signal) -> ExternResult<()> {
if let Signal::MovieLoanHasBeenCreatedForYou(action_hash) = payload {
let movie_loan = get_movie_loan(action_hash)?;
// Send the new movie loan data to the borrower's UI!
emit_signal(LocalSignal::NewMovieLoan(movie_loan))?;
emit_signal(Signal::MovieLoanHasBeenCreatedForYou(movie_loan))?;
}
Ok(())
}
Expand Down
64 changes: 64 additions & 0 deletions src/pages/build/connecting-a-front-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: "Connecting a Front End"
---

::: intro
Front ends connect to a hApp via the **application API** over a local [WebSocket](https://en.wikipedia.org/wiki/WebSocket) interface. They can call [**zome functions**](/build/zome-functions/) and listen to [**signals**](/concepts/9_signals/), and can also do some app management tasks like **cloning**<!--TODO: link --> a cell and getting info about the hApp.
:::

## Where a front end runs

The most important thing to remember about a hApp is that it runs in the Holochain conductor **on each of the devices of the agents themselves**, whether those agents are humans, bots, or headless services. Holochain itself is just an engine for a hApp's back end, so it exposes the hApp's API (its zome functions) and lets front ends connect to the hApp via a WebSocket interface.

This interface is **only exposed to processes on the local device**, not to external network adapters. This helps prevent unauthorized agents from accessing the hApp. It also means the front end must be distributed with the hApp and a Holochain runtime.

Some Holochain runtimes bundle the conductor and a front end host that serves your HTML/JavaScript-based runtimes. Take a look at the [Packaging and Distribution](/get-started/4-packaging-and-distribution/) page from the Get Started guide for the choices.

The Holonix dev environment comes with a runtime called `hc-launch`, which starts Holochain, installs your hApp, and displays a UI for you. The scaffolding tool generates an NPM script that compiles and bundles your back end into a [`.happ` file](/build/happs/#package-a-happ-for-distribution) and starts two instances of the hApp with `hc-launch`. In the root of your project folder, enter:

```bash
npm run start
```

(If you're using a different package manager, change this command to suit.)

## Front-end libraries

Holochain provides front-end client libraries for [JavaScript](https://github.com/holochain/holochain-client-js) and [Rust](https://github.com/holochain/holochain-client-rust). The scaffolding tool generates JavaScript-based UIs that are meant to be served as a single-page app, so we'll focus on JavaScript for this documentation --- or more specifically TypeScript, because that's what the client library is written in.

## Connect to a hApp with the JavaScript client

You connect to the application API with the client's [`AppWebsocket.connect`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) method, which returns a <code>Promise&lt;[AppWebsocket](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md)&gt;</code>.

If you've been using the scaffolding tool with the UI option, all the code to establish a connection to a hApp is already written for you. You can get it to build reasonable bootstrap code for Lit, React, Svelte, and Vue that makes the client available to child components once it's connected to a hApp.

But for now, we're going to give you a simple TypeScript example, inspired by the `ui/index.html` file from a hApp scaffolded with the `vanilla` UI option. We'll call on the function in all the following examples, so that we don't have to create a connection every time.

```typescript
import { AppWebsocket, HolochainError } from '@holochain/client';

const getHolochainClient = (() => {
let client: AppWebsocket | undefined;

return async () => {
if (client === undefined) {
client = await AppWebsocket.connect();
console.log("Connected to Holochain! hApp ID is ${client.installedAppId}");
}
return client;
};
})();

getHolochainClient().catch((error: HolochainError) => console.error(`Connection failure, name ${error.name}, message ${error.message}`));
```

You'll notice that you don't have to pass a connection URI to the client. That's because, at time of writing, all Holochain runtimes that serve a web-based UI will inject a constant into the page that contains the URI, and the client will look for that value. So the scaffolding tool expects you'll be distributing your hApp with one of these runtimes. Check out the [`AppWebsocket.connect` documentation](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) if you're building a front end that runs separately from a Holochain runtime.

## Reference

* [`AppWebsocket`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md)
* [`AppWebsocket.connect`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md)

## Further reading

* [Core Concepts: Application Architecture](/concepts/2_application_architecture/)
54 changes: 54 additions & 0 deletions src/pages/build/connecting-the-parts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: "Connecting the Parts"
---

::: topic-list
### In this section {data-no-toc}

* Connecting the parts (this page)
* [Front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript
* Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent
* Capabilities (coming soon) --- how to manage access to a cell's zome functions
* Working with signals (coming soon) --- receiving notifications from cells
:::

::: intro
Your hApp back end's public interface consists of all the [**zome functions**](/build/zome-functions/) of all the [**cells**](/concepts/2_application_architecture/#cell) instantiated from all the [**DNAs**](/build/dnas/) that fill the hApp's [**roles**](/build/application-structure/#happ). It is accessible to locally running processes and to network peers, and is secured by a form of **capability-based security**<!--TODO: link to that page when it's written. -->, adapted for peer-to-peer applications.

The back end can also send out [**signals**](/concepts/9_signals/)<!--TODO: change this to build guide link when signals is written--> that can be received either by UIs or remote peers.
:::

## What processes can connect to a hApp?

It's worth mentioning again that **all the components of a hApp backend [run on the devices of the individual agents](/build/application-structure/#local)** as cells representing those agents' autonomy --- their capacity to write their own data to their own source chains.

With that in mind, these are the kinds of processes that can make calls to a cell's zome functions:

1. another coordinator zome in the same cell
2. another cell in the same hApp on the agent's device
3. an external client, such as a UI, script, or system service, on the agent's device
4. another peer in the same network as the cell (that is, sharing the same DNA hash)

Of these, only number 3, an external client, can listen to local signals emitted by a zome function. (There's another kind of signal, a remote signal, that's sent between peers in a network. It's actually just a zome function with a special name.)

### How does a process connect?

For cases 1 and 2 above, the Holochain conductor handles inter-process communication between cells. For case 3, Holochain exposes a WebSocket interface for clients to call. And for case 4, the two peers' conductors handle the zome call over the network.

## Securing zome functions against unauthorized access

An agent naturally doesn't want any remote peer calling any of their zome functions, and even local processes should be restricted in case of poorly written DNAs or malware processes on the machine. Holochain uses a modified form of [capability-based security](https://en.wikipedia.org/wiki/Capability-based_security) to secure zome function access.

Capability-based security, in short, says that you should never give out direct access to a resource such as a file or network connection. Instead you mediate access to that resource and give out 'handles' that represent a set of privileges. Holochain expands on this idea by adding the ability to restrict resource access to a certain group of agents, represented by their private keys.

This is a complex topic, so we're going to write a separate page about it soon.<!-- TODO: link when ready -->

## Sending signals for reactive, event-based programming

Zome functions can send out signals, either locally to front ends or remotely to other agents in the same network. This lets you write programs that react to activity rather than having to poll a function for updates. We'll write more about this soon as well!<!--TODO: link when ready-->

## Further reading

* [Core Concepts: Application Architecture](/concepts/2_application_architecture/)
* [Core Concepts: Calls and Capabilities](/concepts/8_calls_capabilities/)
* [Core Concepts: Signals](/concepts/9_signals/)
4 changes: 2 additions & 2 deletions src/pages/build/happs.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ allow_deferred_memproofs: false
Which creates a cell from the DNA immediately on hApp activation.
* `dna`: The DNA that fills the role.
* Location: The place to find the DNA bundle. The three options are:
* `bundled`: Expect the file to be part of this [bundle](#package-a-h-app-for-distribution). The value is a path relative to the manifest file.
* `bundled`: Expect the file to be part of this [bundle](#package-a-happ-for-distribution). The value is a path relative to the manifest file.
* `path`: Get the file from the local filesystem. The value is a filesystem path.
* `url`: Get the file from the web. The value is a URL, of course.
* `modifiers`: Optional [integrity modifiers](/build/dnas/#integrity-modifiers) that change the DNA hash at install time.
Expand Down Expand Up @@ -124,7 +124,7 @@ happ_manifest:
* `ui`: The location of the UI zip file. You can use `bundled`, `path`, or `url`, just like you can with DNAs.
* `happ-manifest`: The location of the hApp back end.

## Package a hApp for distribution
## Package a hApp for distribution {#package-a-happ-for-distribution}

The first step to distributing your hApp is to bundle it into a `.happ` file, then bundle that file and a GUI into a `.webhapp` file. After that, you can go on to packaging it as a standalone binary or distributing it for runtimes that support multiple hApps.

Expand Down
11 changes: 10 additions & 1 deletion src/pages/build/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,13 @@ Now that you've got some basic concepts and the terms we use for them, it's time
* [Identifiers](/build/identifiers) --- working with hashes and other unique IDs
* [Entries](/build/entries/) --- defining, creating, reading, updating, and deleting data
* [Links, Paths, and Anchors](/build/links-paths-and-anchors/) --- creating relationships between data
:::
:::

## Connecting everything together

::: topic-list
* [Overview](/build/connecting-the-parts/) --- zome calls, capabilities, and signals
* [Front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript
* Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent
* Capabilities (coming soon) --- how to manage access to a cell's zome functions
* Working with signals (coming soon) --- receiving notifications from cells

0 comments on commit ee6de8b

Please sign in to comment.