# byhand
> byhand shares one HTML page (an "artifact") through a link. The page is encrypted on the
> sender's machine (AES-256-GCM); the key lives in the link's #fragment and never reaches any
> server. The gateway at https://byhand.page stores nothing: the sender's process serves the encrypted
> page for as long as it keeps running.
## If you were asked to "send" or "share" an artifact on https://byhand.page
1. Write the artifact as ONE self-contained HTML file that follows the rules below,
e.g. `artifact.html`. A small site in a folder (an `index.html` plus CSS, JS and images) also
works: pass the folder instead of the file (`node share.mjs site/`); it is bundled into one page
(scripts and stylesheets inlined, images as data URIs; JS modules importing each other are not
supported), and saving any file in it publishes a new version.
2. Download the sharing script and start it in the background (it never exits on its own),
capturing its output, then read the link. It needs Node.js 22 or newer and nothing else:
```bash
curl -fsSO https://byhand.page/share.mjs
nohup node share.mjs artifact.html > byhand-link.txt 2> byhand.log &
sleep 5; cat byhand-link.txt
```
With an older Node.js, replace `node` by `npx -y -p node@22 node` (and wait a bit longer the
first time). If `byhand-link.txt` is still empty, read `byhand.log` for the reason.
3. Give the user the link exactly as printed. Do not rebuild or shorten it: it has the form
`https://byhand.page/a/#`, and everything after `#` is the decryption key. The link is also
stored in `artifact.html.byhand.json` (field `"link"`).
4. The link only works while that process runs: tell the user so. Stop the process
(`kill `) to stop sharing.
5. To update the artifact, just edit and save the file while the process runs: every saved change
becomes a new version, and pages that are open update by themselves (viewers can still pick older
versions). Restarting the same command later keeps the same link (the signing key, id, decryption
key, versions and comments are kept in `artifact.html.byhand.json`; delete that file to get a new
link).
6. Viewers can comment publicly, write to you privately, and add files. Each event is logged in
`byhand.log` (`comment from on v: …`, `private message from [thread ]: …`,
`file "" … saved as `). While the sharer runs, talk back with:
```bash
node share.mjs inbox artifact.html # everything viewers said, with thread ids
node share.mjs reply artifact.html "Thanks!" # public answer in the comment thread
node share.mjs dm artifact.html "Sure." # private answer to one viewer
```
These commands only queue the message locally and exit; the running process sends it within a
second. Use them when the user asks you to follow up with the people who opened the page.
Options: `--name "Title"` (default: the page's ``), `--url ` (default: https://byhand.page).
### With the `byhand` app
If `command -v byhand` finds it, use it instead of `share.mjs` (no Node.js needed). On Linux you can
also install it first: `curl -fsSL https://byhand.page/install.sh | sh` (into `~/.local/bin`, checksum verified); on Windows, in
PowerShell: `irm https://byhand.page/install.ps1 | iex` (into `%LOCALAPPDATA%\Programs\byhand`, added to the PATH). It does the same
through the gateway (comments, private messages, shared state, files, live versions, same state file)
and also serves the page peer to peer. Same file, same steps 1 and 3 to 5 above:
```bash
nohup byhand share artifact.html --url https://byhand.page > byhand-link.txt 2> byhand.log &
until [ -s byhand-link.txt ]; do sleep 1; done; cat byhand-link.txt
```
Talk to viewers with `byhand inbox artifact.html`, `byhand reply artifact.html "text"` and
`byhand dm artifact.html "text"` (same as the `share.mjs` commands above). Stop it with
`kill -INT `. `byhand share --help` explains the rest. Never run `share.mjs` and the app on the same
file at the same time (they share the state file). Peer to peer, the app publishes in clear that the link
exists and which devices serve it, never the page or its key; `--only web` turns that off.
### If you cannot run commands
Give the user the complete HTML in a single code block, and tell them to save it as a file (for example
`page.html`) and share it from their computer with `byhand share page.html` (the byhand app) or
`node share.mjs page.html` (see https://byhand.page/).
## Artifact rules
The page is shown inside a sandboxed iframe with an opaque origin
(`sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads"`,
without `allow-same-origin`). Therefore:
- One HTML file, 25 MB max. Put CSS and JavaScript inline. No relative URLs: there is no folder
to load other files from.
- External resources must use absolute https URLs. Public CDNs work (cdnjs, jsDelivr, unpkg,
Google Fonts). Images: https URLs, inline SVG, or `data:` URIs.
- `localStorage`, `sessionStorage`, IndexedDB and cookies are unavailable and throw when
accessed. Keep state in memory, or use `window.byhand.state` (below) for data shared by all
viewers.
- Links that leave the page need `target="_blank"` (navigating the top window is blocked).
- Scripts, forms, `alert`/`confirm`, popups and downloads work.
- Set a `` (it becomes the share's name) and
``: links are often opened
on phones.
- Anyone who has the link can read the page: never put secrets in it.
## The `window.byhand` API (optional)
byhand injects `window.byhand` into the page before your scripts run. Use it for pages people
interact with together (polls, checklists, sign-up sheets, counters, shared notes). Every call
returns a Promise. Data is held by the sender's process and travels encrypted; it is only reachable
while the sender is online (writes fail with "the author is offline" otherwise).
- `byhand.viewer()` → `{ id, name }`: a stable random id for this browser and the name the viewer
typed (may be `''`). Not authenticated: do not use it for anything security-related.
- `byhand.setName(name)`: sets the viewer's name (also used for their comments).
- `byhand.state.get(key)` → value or `null`; `byhand.state.all()` → `{ key: value, … }`.
- `byhand.state.set(key, value)`: JSON values only; `value === null` deletes. Keys up to 128 chars,
values up to 16 KB, 500 keys and 1 MB in total. Last write wins: to count votes, store one key
per viewer (e.g. `vote:`) rather than read-modify-write a shared total.
- `byhand.state.subscribe((key, value) => …)` → unsubscribe function: called on every change, by
anyone, including your own writes once they are applied (`value === null` when deleted).
- `byhand.files.put(file)` → `{ id, name, type, size, at, by }`: stores a `File`/`Blob` (5 MB max,
50 MB and 100 files per page) with the sender, visible to every viewer. Typical use: an
`` whose `change` handler calls it.
- `byhand.files.list()` → metadata of all files; `byhand.files.get(id)` → `Blob` (e.g.
`URL.createObjectURL(blob)` to show an image); `byhand.files.delete(id)` (only who added it).
- `byhand.files.subscribe((event) => …)`: called when a file is added or deleted; simplest is to
re-run `byhand.files.list()`.
Always guard optional use with `if (window.byhand)` so the file also works when opened directly.
Viewers can also comment on the page, message the author privately, and the sender can publish new
versions; you do not need to build that into the artifact.