| … | … | @@ -1,9 +1,8 @@ |
| 1 | 1 | # favela-surface |
| 2 | 2 | |
| 3 | | **Read your favela in a browser.** `favela-peer` owns the data; this turns it into pages. |
| 3 | The favela renderer. Server-side HTML over `favela-peer`'s socket. |
| 4 | 4 | |
| 5 | | It is a renderer and only a renderer. Everything it knows it asked the daemon over a unix socket, |
| 6 | | and everything it shows, `favela-markdown` rendered. Close it and you lose nothing but the window. |
| 5 | No storage, no keys, no swarm — it holds a socket and nothing else. |
| 7 | 6 | |
| 8 | 7 | ## Install |
| 9 | 8 | |
| … | … | @@ -19,70 +18,74 @@ favela-peer & # the daemon owns the data |
| 19 | 18 | favela-surface # http://127.0.0.1:4042 |
| 20 | 19 | ``` |
| 21 | 20 | |
| 22 | | **If every page says the daemon is down, it is almost always the socket.** `FAVELA_SOCK` has to |
| 23 | | point at the *daemon's* storage, not the surface's. The two apps have separate defaults, so a |
| 24 | | surface left on its own looks for a socket nobody created. This package's pgh defaults set it to |
| 25 | | `${HOME}/.config/favela-peer/favela.sock`; set it yourself if you run the daemon elsewhere. |
| 21 | ## Config |
| 26 | 22 | |
| 27 | | `FAVELA_SURFACE_PORT` and `FAVELA_SURFACE_HOST` move the listener. It binds `127.0.0.1` — this is |
| 28 | | your own daemon's data with no authentication in front of it, so exposing it is a decision. |
| 23 | | var | default | | |
| 24 | |---|---|---| |
| 25 | | `FAVELA_SOCK` | `${HOME}/.config/favela-peer/favela.sock` | the DAEMON's socket, not the surface's | |
| 26 | | `FAVELA_SURFACE_PORT` | `4042` | | |
| 27 | | `FAVELA_SURFACE_HOST` | `127.0.0.1` | no authentication in front of it | |
| 29 | 28 | |
| 30 | | ## Signing in |
| 29 | Every page reporting "daemon down" almost always means `FAVELA_SOCK` points at the surface's own |
| 30 | storage default rather than the daemon's. The two apps have separate defaults. |
| 31 | 31 | |
| 32 | | Every route is gated except `/login`. A browser gets in with a **one-time invite**, minted on the |
| 33 | | machine running the daemon: |
| 32 | ## Auth |
| 34 | 33 | |
| 35 | | ``` |
| 36 | | favela invite # prints a single-use token |
| 34 | Every route is gated except `/login`. |
| 35 | |
| 36 | ```sh |
| 37 | favela invite # one-time token, minted on the daemon's machine |
| 37 | 38 | favela browsers # who is admitted |
| 38 | 39 | favela revoke <pubkey> # retire one |
| 39 | 40 | ``` |
| 40 | 41 | |
| 41 | | Your browser makes an Ed25519 keypair, keeps it in `localStorage`, and answers a challenge with it. |
| 42 | | There is **no expiry and no password** — the key is the credential, so revoking is the only way out. |
| 42 | - The browser generates an Ed25519 keypair, keeps it in `localStorage`, answers a challenge with it. |
| 43 | - No expiry, no password. The key is the credential; revocation is the only way out. |
| 44 | - A revoke lands on that browser's **next request** — the daemon is asked on every page load. |
| 45 | - Clearing site data means asking for a new invite. |
| 46 | - Sessions are in memory: restarting the surface signs everyone out. |
| 43 | 47 | |
| 44 | | Two consequences worth knowing before they surprise you: |
| 48 | ## JavaScript |
| 45 | 49 | |
| 46 | | - A revoke lands on that browser's **next request**, not whenever a session would have lapsed. The |
| 47 | | daemon is asked whether the key is still allowed on every page load. |
| 48 | | - Clearing site data means asking for a new invite. Nothing on the server remembers you. |
| 49 | | - Restarting the surface signs everyone out, because sessions live in memory. That is a restart, |
| 50 | | not an expiry, and it follows from the surface holding no storage. |
| 50 | Reading needs none. Three pages load a script; two of them require it. |
| 51 | 51 | |
| 52 | | ## JavaScript |
| 52 | | page | loads | JS off | |
| 53 | |---|---|---| |
| 54 | | `/login` | inline module + vendored signer (22 KB) | breaks — no form post makes an Ed25519 signature | |
| 55 | | `/` timeline | `composer.js` (2.6 KB), the `@`-picker | works — posts a real multipart form | |
| 56 | | `/explore` | `explore.js` + three.js (733 KB) | breaks — rotating 3D view | |
| 53 | 57 | |
| 54 | | Reading needs none. Timelines, profiles, posts, search, settings, the graph — all server-rendered |
| 55 | | HTML, and they work with JavaScript switched off. |
| 58 | - `/explore` is the only page that loads three.js. Its legend is server-rendered. |
| 59 | - Everything under `/static/` is vendored, never a CDN: a page loading its signer from someone |
| 60 | else's server would let that server mint signatures with the user's key. |
| 61 | - See `static/README.md` for what is vendored and from where. |
| 56 | 62 | |
| 57 | | Three pages load a script, for three different reasons — and only two of them need one: |
| 63 | ## Routes |
| 58 | 64 | |
| 59 | | | page | what it loads | without JavaScript | |
| 60 | | |---|---|---| |
| 61 | | | `/login` | an inline module + the vendored signer (21K) | **doesn't work** — a form post cannot produce an Ed25519 signature | |
| 62 | | | `/` (the timeline) | `composer.js` (2.6K), the `@`-picker | **works** — it posts a real multipart form; the picker only saves you typing | |
| 63 | | | `/explore` | `explore.js` + three.js (~730K) | **doesn't work** — it is a rotating 3D view | |
| 65 | `/` `/u/<key>` `/r/<key>` `/t/<tag>` `/search` `/settings` `/explore` `/graph` |
| 66 | `/following` `/notifications` `/mentions` `/knocks` `/rooms` `/directory` |
| 67 | |
| 68 | Actions (POST): `/post` `/follow` `/unfollow` `/like` `/unlike` `/react` `/unreact` `/repost` |
| 69 | `/unrepost` `/reply` `/block` `/unblock` `/knock` `/admit` `/peek` `/reach` `/handle` `/theme` |
| 70 | |
| 71 | JSON: `/api/graph` `/api/records` `/api/mentions` `/api/node` |
| 72 | |
| 73 | Auth: `/login` `/logout` `/auth/challenge` `/auth/login` `/auth/redeem` `/auth/claim` `/auth/handoff` |
| 64 | 74 | |
| 65 | | The composer is the pattern to copy. It inserts `@alice` — ordinary text you could have typed — |
| 66 | | and the daemon resolves mentions on the write path either way. With the script missing the composer |
| 67 | | still works; with it present it cannot produce a mention the server would not have produced anyway. |
| 75 | Blobs: `/blob/<oid>` `/avatar/<key>` `/static/<file>` |
| 68 | 76 | |
| 69 | | `/explore` is the one place a browser pays for a megabyte, and only that page loads it. Its legend |
| 70 | | is server-rendered on purpose — an explanation of what the dots mean is no use if it arrives after |
| 71 | | the WebGL. |
| 77 | ## Boundary |
| 72 | 78 | |
| 73 | | **Everything under `/static/` is vendored, never a CDN.** A page that loaded its signer from |
| 74 | | someone else's server would let that server mint signatures with your key. See `static/README.md`. |
| 79 | ⛔ No `corestore`, `hypercore`, `autobee`, `autobase`, `hyperswarm`, `hyper-overlay`, `atproto-*` |
| 80 | or `ap-bridge` — in any source file or dependency. `test/boundary.test.js` enforces it. |
| 75 | 81 | |
| 76 | | ## The boundary |
| 82 | A renderer that grows a Corestore has become a second peer implementation. |
| 77 | 83 | |
| 78 | | ⛔ **No storage, no keys, no swarm.** This process holds a socket and nothing else. A surface that |
| 79 | | grows a Corestore has quietly become a second peer implementation — the fork risk |
| 80 | | `favela-markdown/spec/library.md` §1 names for the codec, which applies here identically. |
| 84 | ## Status |
| 81 | 85 | |
| 82 | | `test/boundary.test.js` enforces it: no source file and no dependency may name `corestore`, |
| 83 | | `hypercore`, `autobee`, `autobase`, `hyperswarm`, `hyper-overlay`, `atproto-*` or `ap-bridge`. |
| 86 | Working. 0.0.47, running on tabertron, ashatron and stortron. |
| 84 | 87 | |
| 85 | | `npm test` — 175 tests, 565 asserts, all passing. |
| 88 | `npm test` — 175 tests, 565 asserts, passing. |
| 86 | 89 | |
| 87 | 90 | ## License |
| 88 | 91 | |