clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/184-task-notice-migration
lofi / website / src / components / TicketAnatomy.tsx
TicketAnatomy.tsx 4.3K
1import type { ReactNode } from "react";2import { useState } from "react";34// An app-connect ticket, opened up. The string is opaque base64url on the5// wire; here the decoded payload is the interface: pick a field, read what6// it does, flip the scope and watch who holds the admin secret.78type FieldKey = "v" | "appId" | "url" | "secret" | "scope" | "label";910const EXPLAIN: Record<FieldKey, { title: string; body: string }> = {11 v: {12 title: "Format version",13 body:14 "Version 1. Parsers treat unknown optional fields as forward-compatible additions, and reject unknown scopes outright rather than granting less than the ticket claims.",15 },16 appId: {17 title: "The store's app id",18 body:19 "Which Jazz app this node hosts. The enrolling app adopts it, unless the deployment pinned its own managed app; a foreign ticket is then refused.",20 },21 url: {22 title: "The gate, verbatim",23 body:24 "Used unchanged as the app's sync server. Jazz clients preserve a base path in the server URL, which is the whole trick that makes the next field work.",25 },26 secret: {27 title: "256-bit secret, riding the path",28 body:29 "Every request the app ever makes (sync WebSockets, catalogue reads) carries it with zero client changes. The node stores only a digest; the string is displayable once.",30 },31 scope: {32 title: "What possession grants",33 body:34 "sync is transport only. provision is a strict superset: store administration, with the gate injecting the node's admin secret server-side. Possession is the opt-in.",35 },36 label: {37 title: "Your bookkeeping",38 body:39 "Issue one ticket per device or context, label it, and revocation stays scoped: one command, live sockets close with code 4001 within seconds.",40 },41};4243export default function TicketAnatomy(): ReactNode {44 const [active, setActive] = useState<FieldKey>("secret");45 const [provision, setProvision] = useState(false);4647 const field = (key: FieldKey, rendered: ReactNode) => (48 <button49 type="button"50 className={`ta-field ${active === key ? "is-active" : ""}`}51 onClick={() => setActive(key)}52 aria-pressed={active === key}53 >54 {rendered}55 </button>56 );5758 return (59 <div className="ta">60 <p className="ta-wire" aria-hidden="true">61 <span className="ta-prefix">lofisync1.</span>62 <span className="ta-blob">63 eyJ2IjoxLCJhcHBJZCI6ImNmZTUyZTQ0LTdhNTktNDIzMi04ZGJi…64 </span>65 <span className="ta-open">⌄ decoded</span>66 </p>6768 <div className="ta-body">69 <pre className="ta-json"><code>{"{\n "}{field("v", <>{'"v"'}: 1</>)}70 {",\n "}71 {field("appId", <>{'"appId"'}: "cfe52e44-7a59-4232-8dbb-bf53f27aeed6"</>)}72 {",\n "}73 {field(74 "url",75 <>76 {'"url"'}: "http://192.168.1.10:4802/t/77 </>,78 )}79 {field("secret", <span className="ta-secret">Yr3…43 chars…9Qk</span>)}80 {field("url", <>"</>)}81 {",\n "}82 {field("scope", <>{'"scope"'}: "{provision ? "provision" : "sync"}"</>)}83 {",\n "}84 {field("label", <>{'"label"'}: "{provision ? "laptop-admin" : "phone"}"</>)}85 {"\n}"}</code></pre>8687 <div className="ta-card" aria-live="polite">88 <h3>{EXPLAIN[active].title}</h3>89 <p>{EXPLAIN[active].body}</p>90 {active === "scope" && (91 <>92 <div className="ta-scope" role="group" aria-label="Ticket scope">93 <button94 type="button"95 className={!provision ? "is-active" : ""}96 onClick={() => setProvision(false)}97 >98 sync99 </button>100 <button101 type="button"102 className={provision ? "is-active" : ""}103 onClick={() => setProvision(true)}104 >105 provision106 </button>107 </div>108 <p className={`ta-inject ${provision ? "is-on" : ""}`}>109 {provision110 ? "X-Jazz-Admin-Secret: injected by the gate. The secret never transits the client."111 : "admin routes answer 401 invalid_ticket, same as an unknown secret. Nothing to enumerate."}112 </p>113 </>114 )}115 </div>116 </div>117 </div>118 );119}