@tollbooth-dpyc/web 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/react/AppShell.d.ts +17 -3
- package/dist/react/AppShell.js +31 -14
- package/dist/react/NpubGate.d.ts +33 -3
- package/dist/react/NpubGate.js +33 -4
- package/dist/react/SignInLinks.d.ts +31 -0
- package/dist/react/SignInLinks.js +6 -0
- package/dist/react/SignInSteps.d.ts +34 -0
- package/dist/react/SignInSteps.js +18 -0
- package/dist/react/index.d.ts +6 -1
- package/dist/react/index.js +5 -0
- package/dist/react/signInFlow.d.ts +26 -0
- package/dist/react/signInFlow.js +42 -0
- package/dist/react/signInTools.d.ts +20 -0
- package/dist/react/signInTools.js +26 -0
- package/dist/react/statusCheck.d.ts +34 -0
- package/dist/react/statusCheck.js +39 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -227,6 +227,10 @@ function Nav() {
|
|
|
227
227
|
the lapsed-sign-in note), or the gate alone. The frame is a column: "page"
|
|
228
228
|
grows with the content; "viewport" pins it to the screen and lets the
|
|
229
229
|
content shrink, so the debug log's spacer never pushes a bottom rail off.
|
|
230
|
+
`shell.statusState` is `"connecting"`, `"ready"` or `"failed"`;
|
|
231
|
+
`shell.statusError` is the failure as one short, scrubbed line; and
|
|
232
|
+
`shell.retryStatus()` runs the check again — so a banner can say
|
|
233
|
+
"connection failed" rather than "Connecting…" through an outage.
|
|
230
234
|
- `SiteNav`: brand, pages (icon, label, badge), `trailing`, and an account
|
|
231
235
|
menu (avatar → identity, links, Log out). Below `collapse` (default
|
|
232
236
|
`(max-width: 639px)`; `false` never) the pages fold into a menu button.
|
|
@@ -243,6 +247,20 @@ function Nav() {
|
|
|
243
247
|
`classNames.spinning`), is disabled and `aria-busy` while `onRefresh`'s
|
|
244
248
|
promise runs or `busy` is set; `label` is the tooltip and accessible name.
|
|
245
249
|
|
|
250
|
+
### Sign-in gate
|
|
251
|
+
|
|
252
|
+
`NpubGate` (or `AppShell`'s `gateOptions`) takes `welcome` — the site's own
|
|
253
|
+
words about what this place is for, above the card. Under the card it shows
|
|
254
|
+
`SignInSteps`, the nine-step sequence (you enter your npub; this page asks the
|
|
255
|
+
operator for a proof; a message reaches your Nostr client; you approve it for
|
|
256
|
+
a time you choose; your signed reply travels back; you tap Verify; the
|
|
257
|
+
operator checks the signature and hands the page a proof; the page unlocks)
|
|
258
|
+
with the step under way lit from the gate's real stage, and `SignInLinks` —
|
|
259
|
+
0xchat and Pricing Studio on the App Store, with the trademark credit
|
|
260
|
+
(`linksCredit={false}` where the page's footer already carries it). Both are
|
|
261
|
+
headless and exported on their own; `classNames.steps` / `classNames.links`
|
|
262
|
+
restyle them inside the gate. `steps={false}` / `links={false}` remove them.
|
|
263
|
+
|
|
246
264
|
### Debug panel
|
|
247
265
|
|
|
248
266
|
`callTool` logs to a shared, scrubbed ring buffer; `DebugPanel` shows it as a
|
package/dist/react/AppShell.d.ts
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* `signedOut` the gate is the page.
|
|
13
13
|
*
|
|
14
14
|
* Inside, `useAppShell()` gives any component the same `shell` — the npub,
|
|
15
|
-
* `signOut`, the `service_status` answer
|
|
16
|
-
*
|
|
15
|
+
* `signOut`, the `service_status` answer and whether that check is still
|
|
16
|
+
* connecting, answered or failed (with `retryStatus()` to ask again) — so a
|
|
17
|
+
* site keeps no session context of its own.
|
|
17
18
|
*
|
|
18
19
|
* Also done here, once: the stored theme applied before paint and followed
|
|
19
20
|
* across tabs; the npub's kind-0 picture seeded as its avatar; `DebugPanel`
|
|
@@ -23,12 +24,20 @@ import { type ReactNode } from "react";
|
|
|
23
24
|
import { type ServiceStatus } from "../standardTools.ts";
|
|
24
25
|
import { type Theme } from "../theme.ts";
|
|
25
26
|
import { type DebugPanelProps } from "./DebugPanel.tsx";
|
|
27
|
+
import { type NpubGateProps } from "./NpubGate.tsx";
|
|
26
28
|
import { type ShellFit } from "./shellLayout.ts";
|
|
29
|
+
import { type StatusState } from "./statusCheck.ts";
|
|
27
30
|
import { type Session } from "./useSession.ts";
|
|
28
31
|
export interface AppShellContext {
|
|
29
32
|
session: Session;
|
|
30
33
|
/** The `service_status` answer, or null until (or unless) it comes. */
|
|
31
34
|
status: ServiceStatus | null;
|
|
35
|
+
/** Where the `service_status` check stands: "connecting", "ready" or "failed". */
|
|
36
|
+
statusState: StatusState;
|
|
37
|
+
/** Why the last check failed — short, human, scrubbed of secrets — or null. */
|
|
38
|
+
statusError: string | null;
|
|
39
|
+
/** Run the `service_status` check again (back to "connecting" until it settles). */
|
|
40
|
+
retryStatus: () => void;
|
|
32
41
|
/** The sign-in gate, ready to place. */
|
|
33
42
|
gate: ReactNode;
|
|
34
43
|
}
|
|
@@ -51,8 +60,13 @@ export interface AppShellProps {
|
|
|
51
60
|
fit?: ShellFit;
|
|
52
61
|
/** Arrive at the gate with a new key already made. */
|
|
53
62
|
startFresh?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The gate's own words and look: the site's `welcome` above the card, whether
|
|
65
|
+
* the sequence strip and the tool links show, and their `classNames`.
|
|
66
|
+
*/
|
|
67
|
+
gateOptions?: Pick<NpubGateProps, "welcome" | "steps" | "links" | "linksCredit" | "classNames">;
|
|
54
68
|
classNames?: AppShellClassNames;
|
|
55
69
|
}
|
|
56
70
|
/** The shell's session, status and gate, for any component inside `AppShell`. */
|
|
57
71
|
export declare function useAppShell(): AppShellContext;
|
|
58
|
-
export default function AppShell({ children, signedOut, footer, debug, theme, fit, startFresh, classNames: c, }: AppShellProps): import("react").JSX.Element;
|
|
72
|
+
export default function AppShell({ children, signedOut, footer, debug, theme, fit, startFresh, gateOptions, classNames: c, }: AppShellProps): import("react").JSX.Element;
|
package/dist/react/AppShell.js
CHANGED
|
@@ -13,20 +13,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
* `signedOut` the gate is the page.
|
|
14
14
|
*
|
|
15
15
|
* Inside, `useAppShell()` gives any component the same `shell` — the npub,
|
|
16
|
-
* `signOut`, the `service_status` answer
|
|
17
|
-
*
|
|
16
|
+
* `signOut`, the `service_status` answer and whether that check is still
|
|
17
|
+
* connecting, answered or failed (with `retryStatus()` to ask again) — so a
|
|
18
|
+
* site keeps no session context of its own.
|
|
18
19
|
*
|
|
19
20
|
* Also done here, once: the stored theme applied before paint and followed
|
|
20
21
|
* across tabs; the npub's kind-0 picture seeded as its avatar; `DebugPanel`
|
|
21
22
|
* mounted last, so its spacer is the page's last thing (see `shellLayout`).
|
|
22
23
|
*/
|
|
23
|
-
import { createContext, useContext, useEffect, useLayoutEffect,
|
|
24
|
+
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useReducer, useRef } from "react";
|
|
24
25
|
import { hydrateAvatarFromNostr } from "../avatar.js";
|
|
26
|
+
import { debugPush } from "../debugLog.js";
|
|
25
27
|
import { serviceStatus } from "../standardTools.js";
|
|
26
28
|
import { bootstrapTheme } from "../theme.js";
|
|
27
29
|
import DebugPanel, {} from "./DebugPanel.js";
|
|
28
|
-
import NpubGate from "./NpubGate.js";
|
|
30
|
+
import NpubGate, {} from "./NpubGate.js";
|
|
29
31
|
import { shellLayout } from "./shellLayout.js";
|
|
32
|
+
import { STATUS_CONNECTING, statusReducer } from "./statusCheck.js";
|
|
30
33
|
import { useSession } from "./useSession.js";
|
|
31
34
|
import { useTheme } from "./useTheme.js";
|
|
32
35
|
const Ctx = createContext(null);
|
|
@@ -37,29 +40,43 @@ export function useAppShell() {
|
|
|
37
40
|
throw new Error("useAppShell must be used inside <AppShell>");
|
|
38
41
|
return shell;
|
|
39
42
|
}
|
|
40
|
-
export default function AppShell({ children, signedOut, footer, debug = {}, theme = "dark", fit = "page", startFresh, classNames: c = {}, }) {
|
|
43
|
+
export default function AppShell({ children, signedOut, footer, debug = {}, theme = "dark", fit = "page", startFresh, gateOptions, classNames: c = {}, }) {
|
|
41
44
|
const session = useSession();
|
|
42
|
-
const [
|
|
45
|
+
const [check, dispatch] = useReducer(statusReducer, STATUS_CONNECTING);
|
|
46
|
+
const attempt = useRef(0);
|
|
43
47
|
useTheme(theme);
|
|
44
48
|
useLayoutEffect(() => {
|
|
45
49
|
bootstrapTheme(theme);
|
|
46
50
|
}, [theme]);
|
|
51
|
+
// Only the newest check settles the state: a slow first answer must not
|
|
52
|
+
// overwrite a retry's, and an unmounted shell settles nothing.
|
|
53
|
+
const retryStatus = useCallback(() => {
|
|
54
|
+
const mine = ++attempt.current;
|
|
55
|
+
dispatch({ type: "start" });
|
|
56
|
+
serviceStatus({ bestEffort: true }).then((s) => {
|
|
57
|
+
if (attempt.current === mine)
|
|
58
|
+
dispatch({ type: "ok", status: s });
|
|
59
|
+
}, (e) => {
|
|
60
|
+
// service_status is a quiet call, so its failure is logged here.
|
|
61
|
+
debugPush("error", `service_status failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
62
|
+
if (attempt.current === mine)
|
|
63
|
+
dispatch({ type: "fail", error: e });
|
|
64
|
+
});
|
|
65
|
+
}, []);
|
|
47
66
|
useEffect(() => {
|
|
48
|
-
|
|
49
|
-
serviceStatus({ bestEffort: true })
|
|
50
|
-
.then((s) => live && setStatus(s))
|
|
51
|
-
.catch(() => live && setStatus(null));
|
|
67
|
+
retryStatus();
|
|
52
68
|
return () => {
|
|
53
|
-
|
|
69
|
+
attempt.current++;
|
|
54
70
|
};
|
|
55
|
-
}, []);
|
|
71
|
+
}, [retryStatus]);
|
|
56
72
|
const { npub, signedIn, notice, refresh } = session;
|
|
57
73
|
useEffect(() => {
|
|
58
74
|
if (signedIn && npub)
|
|
59
75
|
void hydrateAvatarFromNostr(npub);
|
|
60
76
|
}, [signedIn, npub]);
|
|
61
|
-
const
|
|
62
|
-
const
|
|
77
|
+
const { status, state: statusState, error: statusError } = check;
|
|
78
|
+
const gate = (_jsx(NpubGate, { onLogin: refresh, operatorHash: status?.operator_npub_hash, notice: notice, startFresh: startFresh, ...gateOptions }));
|
|
79
|
+
const shell = { session, status, statusState, statusError, retryStatus, gate };
|
|
63
80
|
const layout = shellLayout(fit);
|
|
64
81
|
return (_jsx(Ctx.Provider, { value: shell, children: _jsxs("div", { className: c.root, style: layout.root, children: [_jsx("div", { className: c.body, style: layout.body, children: signedIn ? children(shell) : signedOut ? signedOut(shell) : gate }), typeof footer === "function" ? footer(shell) : footer, debug !== false && _jsx(DebugPanel, { ...debug })] }) }));
|
|
65
82
|
}
|
package/dist/react/NpubGate.d.ts
CHANGED
|
@@ -4,13 +4,43 @@
|
|
|
4
4
|
* begin → request_npub_proof → awaiting reply → receive_npub_proof → app
|
|
5
5
|
*
|
|
6
6
|
* Success is not a `verified` flag but "no `error` and a token came back".
|
|
7
|
+
*
|
|
8
|
+
* Around the card: the site's `welcome` (its own words about what this place
|
|
9
|
+
* is for), the network's register line, and — below — `SignInSteps`, which
|
|
10
|
+
* shows the sequence above with the step under way lit from this component's
|
|
11
|
+
* real stage, and `SignInLinks`, the two tools a newcomer needs. Both take the
|
|
12
|
+
* gate's default look here; a site restyles them through `classNames`.
|
|
7
13
|
*/
|
|
8
|
-
|
|
14
|
+
import { type ReactNode } from "react";
|
|
15
|
+
import { type SignInLinksClassNames } from "./SignInLinks.tsx";
|
|
16
|
+
import { type SignInStepsClassNames } from "./SignInSteps.tsx";
|
|
17
|
+
export interface NpubGateClassNames {
|
|
18
|
+
root?: string;
|
|
19
|
+
/** Around the site's `welcome`. */
|
|
20
|
+
welcome?: string;
|
|
21
|
+
/** The network's register line under the heading. */
|
|
22
|
+
register?: string;
|
|
23
|
+
/** The sequence strip's parts; the gate's defaults apply where unset. */
|
|
24
|
+
steps?: SignInStepsClassNames;
|
|
25
|
+
/** The tools list's parts; the gate's defaults apply where unset. */
|
|
26
|
+
links?: SignInLinksClassNames;
|
|
27
|
+
}
|
|
28
|
+
export interface NpubGateProps {
|
|
9
29
|
onLogin: () => void;
|
|
10
30
|
/** Arrive with a key already made, for a first-timer who asked for one. */
|
|
11
31
|
startFresh?: boolean;
|
|
12
|
-
/** Operator fingerprint, shown so the patron can check who sent the
|
|
32
|
+
/** Operator fingerprint, shown so the patron can check who sent the message. */
|
|
13
33
|
operatorHash?: string;
|
|
14
34
|
/** A routine re-auth prompt, drawn as a calm note rather than an error. */
|
|
15
35
|
notice?: string;
|
|
16
|
-
|
|
36
|
+
/** The site's own words about what this place is for, above the card. */
|
|
37
|
+
welcome?: ReactNode;
|
|
38
|
+
/** The sequence strip under the card. Default true. */
|
|
39
|
+
steps?: boolean;
|
|
40
|
+
/** The 0xchat / Pricing Studio links under the strip. Default true. */
|
|
41
|
+
links?: boolean;
|
|
42
|
+
/** The trademark credit under the links. Default true; false when the page's footer carries it. */
|
|
43
|
+
linksCredit?: boolean;
|
|
44
|
+
classNames?: NpubGateClassNames;
|
|
45
|
+
}
|
|
46
|
+
export default function NpubGate({ onLogin, startFresh, operatorHash, notice, welcome, steps, links, linksCredit, classNames: c, }: NpubGateProps): import("react").JSX.Element;
|
package/dist/react/NpubGate.js
CHANGED
|
@@ -5,6 +5,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
5
5
|
* begin → request_npub_proof → awaiting reply → receive_npub_proof → app
|
|
6
6
|
*
|
|
7
7
|
* Success is not a `verified` flag but "no `error` and a token came back".
|
|
8
|
+
*
|
|
9
|
+
* Around the card: the site's `welcome` (its own words about what this place
|
|
10
|
+
* is for), the network's register line, and — below — `SignInSteps`, which
|
|
11
|
+
* shows the sequence above with the step under way lit from this component's
|
|
12
|
+
* real stage, and `SignInLinks`, the two tools a newcomer needs. Both take the
|
|
13
|
+
* gate's default look here; a site restyles them through `classNames`.
|
|
8
14
|
*/
|
|
9
15
|
import { useEffect, useRef, useState } from "react";
|
|
10
16
|
import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools";
|
|
@@ -13,8 +19,31 @@ import { forgetRecentLogin, getLastTypedNpub, getValidRecentLogins, recordRecent
|
|
|
13
19
|
import { setSessionNsec } from "../sessionNsec.js";
|
|
14
20
|
import { readSignInFailure } from "../signInSituation.js";
|
|
15
21
|
import { receiveNpubProof, requestNpubProof } from "../standardTools.js";
|
|
22
|
+
import { cx } from "./cx.js";
|
|
23
|
+
import SignInLinks, {} from "./SignInLinks.js";
|
|
24
|
+
import SignInSteps, {} from "./SignInSteps.js";
|
|
25
|
+
import { gateStage } from "./signInFlow.js";
|
|
16
26
|
import { card, errBox, ghost, input, muted, primary, warnBox } from "./ui.js";
|
|
17
|
-
|
|
27
|
+
const STEPS_LOOK = {
|
|
28
|
+
root: "mt-6 space-y-1.5",
|
|
29
|
+
step: "grid grid-cols-[1.5rem_auto_1fr] items-baseline gap-x-2 rounded-lg border px-3 py-1.5 text-xs transition-colors",
|
|
30
|
+
done: "border-transparent text-[var(--tb-muted)] opacity-70",
|
|
31
|
+
current: "border-[var(--tb-accent)] bg-[var(--tb-surface)] text-[var(--tb-ink)]",
|
|
32
|
+
todo: "border-transparent text-[var(--tb-muted)]",
|
|
33
|
+
index: "font-mono text-[10px] tabular-nums",
|
|
34
|
+
actor: "font-medium",
|
|
35
|
+
label: "",
|
|
36
|
+
};
|
|
37
|
+
const LINKS_LOOK = {
|
|
38
|
+
root: "mt-6 space-y-2",
|
|
39
|
+
lead: `text-xs ${muted}`,
|
|
40
|
+
list: "space-y-1.5",
|
|
41
|
+
link: `${card} block px-3 py-2.5 hover:border-[var(--tb-accent)] transition-colors`,
|
|
42
|
+
name: "block text-sm font-medium",
|
|
43
|
+
line: `block text-xs ${muted}`,
|
|
44
|
+
credit: `text-[10px] leading-relaxed ${muted}`,
|
|
45
|
+
};
|
|
46
|
+
export default function NpubGate({ onLogin, startFresh = false, operatorHash, notice, welcome, steps = true, links = true, linksCredit = true, classNames: c = {}, }) {
|
|
18
47
|
const { appName } = tollboothConfig();
|
|
19
48
|
// Prefilled from what was typed last, NOT from the stored identity.
|
|
20
49
|
const [value, setValue] = useState(getLastTypedNpub());
|
|
@@ -133,7 +162,7 @@ export default function NpubGate({ onLogin, startFresh = false, operatorHash, no
|
|
|
133
162
|
setPendingProof("");
|
|
134
163
|
setError("");
|
|
135
164
|
}
|
|
136
|
-
return (_jsxs("div", { className: "max-w-md mx-auto mt-12 px-4", children: [_jsxs("h1", { className: "text-xl font-semibold mb-1", children: ["Sign in to ", appName] }), _jsx("p", { className: `text-sm mb-5 ${muted}`, children: "
|
|
165
|
+
return (_jsxs("div", { className: cx("max-w-md mx-auto mt-12 px-4", c.root), children: [_jsxs("h1", { className: "text-xl font-semibold mb-1", children: ["Sign in to ", appName] }), welcome && _jsx("div", { className: cx("text-sm mb-3", c.welcome), children: welcome }), _jsxs("p", { className: cx(`text-sm mb-5 ${muted}`, c.register), children: [appName, " is an operator on the Tollbooth DPYC\u2122 network. DPYC\u2122 \u2014 Don't Pester Your Customer\u2122: no email, no password, no KYC. Your Nostr key is your identity."] }), notice && _jsx("div", { className: `mb-5 ${warnBox}`, children: notice }), stage === "begin" && recents.length > 0 && (_jsxs("div", { className: "mb-5", children: [_jsx("div", { className: `text-xs uppercase tracking-wider mb-2 ${muted}`, children: "Recent identities" }), _jsx("div", { className: "space-y-1.5", children: recents.map((e) => (_jsxs("div", { className: "flex gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => reuseRecent(e), disabled: busy, title: `Re-enter as ${e.npub} on the cached proof`, className: `${card} flex-1 flex items-center justify-between px-3 py-2.5 text-left hover:border-[var(--tb-accent)]`, children: [_jsxs("span", { className: "font-mono text-xs truncate", children: [e.npub.slice(0, 12), "\u2026", e.npub.slice(-6)] }), _jsxs("span", { className: `text-xs shrink-0 ml-2 ${muted}`, children: [ttl(e.expiresAt), " left"] })] }), _jsx("button", { type: "button", onClick: () => forget(e.npub), disabled: busy, title: "Forget this identity", "aria-label": "Forget this identity", className: `w-9 rounded-lg border border-[var(--tb-line)] ${muted}`, children: "\u00D7" })] }, e.npub))) })] })), _jsxs("div", { className: `${card} p-4 space-y-3`, children: [stage === "begin" ? (_jsxs(_Fragment, { children: [_jsx("label", { className: `block text-xs uppercase tracking-wider ${muted}`, htmlFor: "tb-npub-field", children: "Paste your npub or nsec" }), _jsx("input", { id: "tb-npub-field", type: isNsec ? "password" : "text", value: value, onChange: (e) => {
|
|
137
166
|
setValue(e.target.value);
|
|
138
167
|
setGeneratedHint(false);
|
|
139
168
|
}, onKeyDown: (e) => {
|
|
@@ -143,9 +172,9 @@ export default function NpubGate({ onLogin, startFresh = false, operatorHash, no
|
|
|
143
172
|
signInWithNsec();
|
|
144
173
|
else
|
|
145
174
|
void begin();
|
|
146
|
-
}, placeholder: "npub1\u2026 (
|
|
175
|
+
}, placeholder: "npub1\u2026 (a message to your client) or nsec1\u2026 (instant)", spellCheck: false, autoCapitalize: "off", autoCorrect: "off", className: input }), generatedHint && (_jsxs("div", { className: warnBox, children: ["New key generated. ", _jsx("b", { children: "Save this nsec" }), " somewhere safe \u2014 it's the only copy and it lives only in this browser."] })), _jsx("button", { type: "button", onClick: () => (isNsec ? signInWithNsec() : void begin()), disabled: !valid || busy, className: primary, children: busy ? "Sending…" : isNpub ? "Send message" : "Sign in" }), _jsx("p", { className: `text-xs ${muted}`, children: isNsec
|
|
147
176
|
? "Your nsec stays in this browser and signs each call."
|
|
148
|
-
: "We send a
|
|
177
|
+
: "We send a message to your npub. Approve it from your Nostr client — your signature is the proof." }), _jsx("button", { type: "button", onClick: generateKey, className: ghost, children: "Generate a new key" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: `${warnBox} space-y-1.5`, children: [_jsx("div", { className: "font-medium", children: "Message sent \u2014 open your Nostr client." }), _jsx("div", { children: "Reply with any text. Your signature on that reply is the proof." }), operatorHash && (_jsxs("div", { children: ["Sender fingerprint: ", _jsxs("span", { className: "font-mono", children: ["\uD83D\uDD12 ", operatorHash] })] }))] }), pendingProof && (_jsxs("div", { className: "rounded-lg p-3 text-xs border border-[var(--tb-accent)] space-y-2", children: [_jsx("div", { className: `uppercase tracking-wider text-[10px] ${muted}`, children: "Confirmation code" }), _jsx("div", { className: "font-mono text-base select-all", children: pendingProof }), _jsxs("div", { className: `leading-relaxed ${muted}`, children: ["The same code appears in the message. ", _jsx("b", { children: "Approve it only if the codes match." }), " If they differ \u2014 or the message points you somewhere other than this site \u2014 do not reply."] })] })), _jsx("button", { type: "button", onClick: () => void finish(), disabled: busy, className: primary, children: busy ? "Checking…" : "I've replied — verify" }), _jsx("button", { type: "button", onClick: () => void begin(), disabled: busy, className: ghost, children: "Resend" }), _jsx("button", { type: "button", onClick: reset, disabled: busy, className: `w-full text-xs py-1.5 ${muted}`, children: "Use a different npub" })] })), error && _jsx(Trouble, { raw: error, appName: appName }), note && _jsx("div", { className: `text-xs text-center italic ${muted}`, children: note })] }), steps && _jsx(SignInSteps, { stage: gateStage(stage, busy), classNames: { ...STEPS_LOOK, ...c.steps } }), links && _jsx(SignInLinks, { credit: linksCredit, classNames: { ...LINKS_LOOK, ...c.links } })] }));
|
|
149
178
|
}
|
|
150
179
|
/** A failure, read to the person: the lead first, the service's own words underneath. */
|
|
151
180
|
function Trouble({ raw, appName }) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two tools a newcomer needs, offered under the sign-in card: a Nostr
|
|
3
|
+
* client and Pricing Studio. Links open in a new tab; the credit line for
|
|
4
|
+
* other companies' marks is on by default and off where the page's own legal
|
|
5
|
+
* footer already carries it.
|
|
6
|
+
*
|
|
7
|
+
* Mechanics only: the list, the targets and the credit are the package's;
|
|
8
|
+
* every class is the site's.
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import { type SignInLink } from "./signInTools.ts";
|
|
12
|
+
export interface SignInLinksClassNames {
|
|
13
|
+
root?: string;
|
|
14
|
+
lead?: string;
|
|
15
|
+
list?: string;
|
|
16
|
+
item?: string;
|
|
17
|
+
link?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
line?: string;
|
|
20
|
+
credit?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SignInLinksProps {
|
|
23
|
+
/** Default: 0xchat and Pricing Studio. */
|
|
24
|
+
links?: readonly SignInLink[];
|
|
25
|
+
/** A line above the list. Default "New to Nostr? These two are all it takes." */
|
|
26
|
+
lead?: ReactNode;
|
|
27
|
+
/** The trademark credit. Default true; false when the page's footer carries it. */
|
|
28
|
+
credit?: boolean;
|
|
29
|
+
classNames?: SignInLinksClassNames;
|
|
30
|
+
}
|
|
31
|
+
export default function SignInLinks({ links, lead, credit, classNames: c, }: SignInLinksProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
3
|
+
import { SIGN_IN_LINKS, SIGN_IN_LINKS_CREDIT } from "./signInTools.js";
|
|
4
|
+
export default function SignInLinks({ links = SIGN_IN_LINKS, lead = "New to Nostr? These two are all it takes.", credit = true, classNames: c = {}, }) {
|
|
5
|
+
return (_jsxs("nav", { "aria-label": "Tools for signing in", className: c.root, children: [lead && _jsx("p", { className: c.lead, children: lead }), _jsx("ul", { className: c.list, children: links.map((l) => (_jsx("li", { className: c.item, children: _jsxs("a", { href: l.href, target: "_blank", rel: "noopener noreferrer", className: cx(c.link), children: [_jsx("span", { className: c.name, children: l.name }), _jsx("span", { className: c.line, children: l.line })] }) }, l.id))) }), credit && _jsx("p", { className: c.credit, children: SIGN_IN_LINKS_CREDIT })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sequence under the sign-in card: what happens, who does it, and which
|
|
3
|
+
* step is under way now — read from the gate's real stage, never guessed.
|
|
4
|
+
*
|
|
5
|
+
* Mechanics only. It renders an ordered list with `data-state` on each step
|
|
6
|
+
* (`done` / `current` / `todo`) and `aria-current="step"` on the ones under
|
|
7
|
+
* way; every class is the site's through `classNames`. Without them the
|
|
8
|
+
* markup is plain and inherits from the page.
|
|
9
|
+
*/
|
|
10
|
+
import { type SignInActor, type SignInStage, type SignInStep } from "./signInFlow.ts";
|
|
11
|
+
export interface SignInStepsClassNames {
|
|
12
|
+
root?: string;
|
|
13
|
+
step?: string;
|
|
14
|
+
/** Added to a step by its state. */
|
|
15
|
+
done?: string;
|
|
16
|
+
current?: string;
|
|
17
|
+
todo?: string;
|
|
18
|
+
/** The step number. */
|
|
19
|
+
index?: string;
|
|
20
|
+
/** Who acts. */
|
|
21
|
+
actor?: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SignInStepsProps {
|
|
25
|
+
stage: SignInStage;
|
|
26
|
+
/** Default: the package's nine. */
|
|
27
|
+
steps?: readonly SignInStep[];
|
|
28
|
+
/** Names for the actors. Default: You / This page / The operator / Your Nostr client. */
|
|
29
|
+
actors?: Readonly<Record<SignInActor, string>>;
|
|
30
|
+
/** Accessible name of the list. Default "How signing in works". */
|
|
31
|
+
label?: string;
|
|
32
|
+
classNames?: SignInStepsClassNames;
|
|
33
|
+
}
|
|
34
|
+
export default function SignInSteps({ stage, steps, actors, label, classNames: c, }: SignInStepsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The sequence under the sign-in card: what happens, who does it, and which
|
|
4
|
+
* step is under way now — read from the gate's real stage, never guessed.
|
|
5
|
+
*
|
|
6
|
+
* Mechanics only. It renders an ordered list with `data-state` on each step
|
|
7
|
+
* (`done` / `current` / `todo`) and `aria-current="step"` on the ones under
|
|
8
|
+
* way; every class is the site's through `classNames`. Without them the
|
|
9
|
+
* markup is plain and inherits from the page.
|
|
10
|
+
*/
|
|
11
|
+
import { cx } from "./cx.js";
|
|
12
|
+
import { SIGN_IN_ACTORS, SIGN_IN_STEPS, stepState, } from "./signInFlow.js";
|
|
13
|
+
export default function SignInSteps({ stage, steps = SIGN_IN_STEPS, actors = SIGN_IN_ACTORS, label = "How signing in works", classNames: c = {}, }) {
|
|
14
|
+
return (_jsx("ol", { "aria-label": label, className: c.root, children: steps.map((s, i) => {
|
|
15
|
+
const state = stepState(s, stage);
|
|
16
|
+
return (_jsxs("li", { "data-state": state, "aria-current": state === "current" ? "step" : undefined, className: cx(c.step, c[state]), children: [_jsx("span", { "aria-hidden": true, className: c.index, children: i + 1 }), _jsx("span", { className: c.actor, children: actors[s.actor] }), _jsx("span", { className: c.label, children: s.label })] }, s.id));
|
|
17
|
+
}) }));
|
|
18
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as AccountPage, type AccountPageClassNames, type AccountPageProps, type AccountSectionText, type AccountThemeProps, type AccountTimezoneProps, } from "./AccountPage.tsx";
|
|
2
2
|
export { ACCOUNT_SECTIONS, type AccountSection } from "./accountSections.ts";
|
|
3
3
|
export { default as AppShell, useAppShell, type AppShellClassNames, type AppShellContext, type AppShellProps, } from "./AppShell.tsx";
|
|
4
|
+
export { statusErrorMessage, type StatusState } from "./statusCheck.ts";
|
|
4
5
|
export type { ShellFit } from "./shellLayout.ts";
|
|
5
6
|
export { default as Avatar } from "./Avatar.tsx";
|
|
6
7
|
export { default as AvatarPicker } from "./AvatarPicker.tsx";
|
|
@@ -9,7 +10,11 @@ export { default as CouponsPanel, type CouponsPanelProps, type CouponsPanelClass
|
|
|
9
10
|
export { default as DebugPanel, type DebugPanelProps } from "./DebugPanel.tsx";
|
|
10
11
|
export { default as ErrorBoundary, type ErrorBoundaryProps, type ErrorBoundaryClassNames, type ErrorFallbackProps, } from "./ErrorBoundary.tsx";
|
|
11
12
|
export { OperatorFundingStatus, PatronFundingStatus, type FundingStatusClassNames, type FundingStatusProps, type OperatorFundingStatusProps, type PatronFundingStatusProps, } from "./FundingStatus.tsx";
|
|
12
|
-
export { default as NpubGate } from "./NpubGate.tsx";
|
|
13
|
+
export { default as NpubGate, type NpubGateClassNames, type NpubGateProps } from "./NpubGate.tsx";
|
|
14
|
+
export { default as SignInSteps, type SignInStepsClassNames, type SignInStepsProps } from "./SignInSteps.tsx";
|
|
15
|
+
export { gateStage, SIGN_IN_ACTORS, SIGN_IN_STEPS, stepState, type SignInActor, type SignInStage, type SignInStep, type SignInStepState, } from "./signInFlow.ts";
|
|
16
|
+
export { default as SignInLinks, type SignInLinksClassNames, type SignInLinksProps } from "./SignInLinks.tsx";
|
|
17
|
+
export { SIGN_IN_LINKS, SIGN_IN_LINKS_CREDIT, type SignInLink } from "./signInTools.ts";
|
|
13
18
|
export { default as NostrProfilePanel } from "./NostrProfilePanel.tsx";
|
|
14
19
|
export { PageControls, SortHeader, TableShell, type PageControlsProps, type PageControlsClassNames, type SortHeaderProps, type SortHeaderClassNames, type TableShellClassNames, } from "./PagedTable.tsx";
|
|
15
20
|
export { default as RefreshButton, type RefreshButtonClassNames, type RefreshButtonProps, } from "./RefreshButton.tsx";
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as AccountPage, } from "./AccountPage.js";
|
|
2
2
|
export { ACCOUNT_SECTIONS } from "./accountSections.js";
|
|
3
3
|
export { default as AppShell, useAppShell, } from "./AppShell.js";
|
|
4
|
+
export { statusErrorMessage } from "./statusCheck.js";
|
|
4
5
|
export { default as Avatar } from "./Avatar.js";
|
|
5
6
|
export { default as AvatarPicker } from "./AvatarPicker.js";
|
|
6
7
|
export { default as BuildInfoPanel, } from "./BuildInfoPanel.js";
|
|
@@ -9,6 +10,10 @@ export { default as DebugPanel } from "./DebugPanel.js";
|
|
|
9
10
|
export { default as ErrorBoundary, } from "./ErrorBoundary.js";
|
|
10
11
|
export { OperatorFundingStatus, PatronFundingStatus, } from "./FundingStatus.js";
|
|
11
12
|
export { default as NpubGate } from "./NpubGate.js";
|
|
13
|
+
export { default as SignInSteps } from "./SignInSteps.js";
|
|
14
|
+
export { gateStage, SIGN_IN_ACTORS, SIGN_IN_STEPS, stepState, } from "./signInFlow.js";
|
|
15
|
+
export { default as SignInLinks } from "./SignInLinks.js";
|
|
16
|
+
export { SIGN_IN_LINKS, SIGN_IN_LINKS_CREDIT } from "./signInTools.js";
|
|
12
17
|
export { default as NostrProfilePanel } from "./NostrProfilePanel.js";
|
|
13
18
|
export { PageControls, SortHeader, TableShell, } from "./PagedTable.js";
|
|
14
19
|
export { default as RefreshButton, } from "./RefreshButton.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of `SignInSteps` (named apart from it: the filesystem is case-blind): what happens when someone signs in, in the
|
|
3
|
+
* order it happens, and which step is under way for a given gate stage.
|
|
4
|
+
*
|
|
5
|
+
* Nine steps, four actors. The words are the visitor's, not the protocol's:
|
|
6
|
+
* "the operator" (never MCP), "a proof" (never dpop), "this page" (never FE).
|
|
7
|
+
* A site may pass its own list to `SignInSteps`; this one is the default.
|
|
8
|
+
*/
|
|
9
|
+
/** Where the gate stands. `sending` is `begin` with a request in flight. */
|
|
10
|
+
export type SignInStage = "begin" | "sending" | "awaiting" | "checking" | "done";
|
|
11
|
+
export type SignInActor = "you" | "page" | "operator" | "client";
|
|
12
|
+
export interface SignInStep {
|
|
13
|
+
id: string;
|
|
14
|
+
actor: SignInActor;
|
|
15
|
+
/** Short, on screen. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** The stage during which this step is the one under way. */
|
|
18
|
+
at: SignInStage;
|
|
19
|
+
}
|
|
20
|
+
export type SignInStepState = "done" | "current" | "todo";
|
|
21
|
+
export declare const SIGN_IN_ACTORS: Readonly<Record<SignInActor, string>>;
|
|
22
|
+
export declare const SIGN_IN_STEPS: readonly SignInStep[];
|
|
23
|
+
/** Whether `step` is behind, under way, or ahead of `stage`. */
|
|
24
|
+
export declare function stepState(step: SignInStep, stage: SignInStage): SignInStepState;
|
|
25
|
+
/** The gate's private stage and busy flag, read as one `SignInStage`. */
|
|
26
|
+
export declare function gateStage(stage: "begin" | "awaiting" | "checking", busy: boolean): SignInStage;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of `SignInSteps` (named apart from it: the filesystem is case-blind): what happens when someone signs in, in the
|
|
3
|
+
* order it happens, and which step is under way for a given gate stage.
|
|
4
|
+
*
|
|
5
|
+
* Nine steps, four actors. The words are the visitor's, not the protocol's:
|
|
6
|
+
* "the operator" (never MCP), "a proof" (never dpop), "this page" (never FE).
|
|
7
|
+
* A site may pass its own list to `SignInSteps`; this one is the default.
|
|
8
|
+
*/
|
|
9
|
+
export const SIGN_IN_ACTORS = {
|
|
10
|
+
you: "You",
|
|
11
|
+
page: "This page",
|
|
12
|
+
operator: "The operator",
|
|
13
|
+
client: "Your Nostr client",
|
|
14
|
+
};
|
|
15
|
+
export const SIGN_IN_STEPS = [
|
|
16
|
+
{ id: "enter", actor: "you", label: "Enter your npub", at: "begin" },
|
|
17
|
+
{ id: "ask", actor: "page", label: "Asks the operator for a proof", at: "sending" },
|
|
18
|
+
{ id: "message", actor: "operator", label: "Sends a message to your Nostr client", at: "awaiting" },
|
|
19
|
+
{ id: "approve", actor: "you", label: "Approve it, for as long as you choose", at: "awaiting" },
|
|
20
|
+
{ id: "reply", actor: "client", label: "Your signed reply travels back", at: "awaiting" },
|
|
21
|
+
{ id: "verify", actor: "you", label: "Tap Verify", at: "awaiting" },
|
|
22
|
+
{ id: "check", actor: "operator", label: "Checks your signature", at: "checking" },
|
|
23
|
+
{ id: "token", actor: "operator", label: "Hands this page a proof", at: "checking" },
|
|
24
|
+
{ id: "unlock", actor: "page", label: "Unlocks", at: "done" },
|
|
25
|
+
];
|
|
26
|
+
const ORDER = ["begin", "sending", "awaiting", "checking", "done"];
|
|
27
|
+
/** Whether `step` is behind, under way, or ahead of `stage`. */
|
|
28
|
+
export function stepState(step, stage) {
|
|
29
|
+
const now = ORDER.indexOf(stage);
|
|
30
|
+
const at = ORDER.indexOf(step.at);
|
|
31
|
+
if (at < now)
|
|
32
|
+
return "done";
|
|
33
|
+
if (at === now)
|
|
34
|
+
return "current";
|
|
35
|
+
return "todo";
|
|
36
|
+
}
|
|
37
|
+
/** The gate's private stage and busy flag, read as one `SignInStage`. */
|
|
38
|
+
export function gateStage(stage, busy) {
|
|
39
|
+
if (stage === "begin")
|
|
40
|
+
return busy ? "sending" : "begin";
|
|
41
|
+
return stage;
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a visitor gets the two tools the sign-in leans on: a Nostr client that
|
|
3
|
+
* holds their key and answers the message, and Pricing Studio, where the
|
|
4
|
+
* operators of this network price their tools.
|
|
5
|
+
*
|
|
6
|
+
* 0xchat links to its own download page rather than to one store, so the
|
|
7
|
+
* link stays right as its listings move. Pricing Studio's App Store page is
|
|
8
|
+
* public (id 6760925205). Apple's wording rule: an app is *available on the
|
|
9
|
+
* App Store*, never *at*.
|
|
10
|
+
*/
|
|
11
|
+
export interface SignInLink {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
href: string;
|
|
15
|
+
/** One short line under the name. */
|
|
16
|
+
line: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const SIGN_IN_LINKS: readonly SignInLink[];
|
|
19
|
+
/** Shown once, small, wherever the links are — unless the page already carries a legal footer. */
|
|
20
|
+
export declare const SIGN_IN_LINKS_CREDIT = "App Store is a trademark of Apple Inc., registered in the U.S. and other countries. 0xchat is a trademark of its owner.";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a visitor gets the two tools the sign-in leans on: a Nostr client that
|
|
3
|
+
* holds their key and answers the message, and Pricing Studio, where the
|
|
4
|
+
* operators of this network price their tools.
|
|
5
|
+
*
|
|
6
|
+
* 0xchat links to its own download page rather than to one store, so the
|
|
7
|
+
* link stays right as its listings move. Pricing Studio's App Store page is
|
|
8
|
+
* public (id 6760925205). Apple's wording rule: an app is *available on the
|
|
9
|
+
* App Store*, never *at*.
|
|
10
|
+
*/
|
|
11
|
+
export const SIGN_IN_LINKS = [
|
|
12
|
+
{
|
|
13
|
+
id: "0xchat",
|
|
14
|
+
name: "0xchat",
|
|
15
|
+
href: "https://0xchat.com",
|
|
16
|
+
line: "A Nostr client for iPhone, Android and desktop. It holds your key and answers the sign-in message.",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "pricing-studio",
|
|
20
|
+
name: "Pricing Studio",
|
|
21
|
+
href: "https://apps.apple.com/us/app/pricing-studio/id6760925205",
|
|
22
|
+
line: "Where operators price their tools, live. Available on the App Store.",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/** Shown once, small, wherever the links are — unless the page already carries a legal footer. */
|
|
26
|
+
export const SIGN_IN_LINKS_CREDIT = "App Store is a trademark of Apple Inc., registered in the U.S. and other countries. 0xchat is a trademark of its owner.";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's `service_status` check as three states a site can tell apart:
|
|
3
|
+
* still connecting, answered, or failed. Before this a failed check read as
|
|
4
|
+
* "no answer yet", so a banner said "Connecting…" through a whole outage.
|
|
5
|
+
*
|
|
6
|
+
* Pure: `AppShell` feeds it events, and the tests feed it the same ones.
|
|
7
|
+
*/
|
|
8
|
+
import type { ServiceStatus } from "../standardTools.ts";
|
|
9
|
+
export type StatusState = "connecting" | "ready" | "failed";
|
|
10
|
+
export interface StatusCheck {
|
|
11
|
+
state: StatusState;
|
|
12
|
+
/** The last `service_status` answer; kept through a retry. */
|
|
13
|
+
status: ServiceStatus | null;
|
|
14
|
+
/** Why the last check failed — short, human, scrubbed — or null. */
|
|
15
|
+
error: string | null;
|
|
16
|
+
}
|
|
17
|
+
export type StatusEvent = {
|
|
18
|
+
type: "start";
|
|
19
|
+
} | {
|
|
20
|
+
type: "ok";
|
|
21
|
+
status: ServiceStatus;
|
|
22
|
+
} | {
|
|
23
|
+
type: "fail";
|
|
24
|
+
error: unknown;
|
|
25
|
+
};
|
|
26
|
+
export declare const STATUS_CONNECTING: StatusCheck;
|
|
27
|
+
/** The longest `statusError` a banner is handed. */
|
|
28
|
+
export declare const STATUS_ERROR_MAX = 140;
|
|
29
|
+
/**
|
|
30
|
+
* A failed check as one short line: scrubbed of secrets, the runtime tool
|
|
31
|
+
* name dropped, whitespace collapsed, cut at `STATUS_ERROR_MAX`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function statusErrorMessage(error: unknown): string;
|
|
34
|
+
export declare function statusReducer(check: StatusCheck, event: StatusEvent): StatusCheck;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's `service_status` check as three states a site can tell apart:
|
|
3
|
+
* still connecting, answered, or failed. Before this a failed check read as
|
|
4
|
+
* "no answer yet", so a banner said "Connecting…" through a whole outage.
|
|
5
|
+
*
|
|
6
|
+
* Pure: `AppShell` feeds it events, and the tests feed it the same ones.
|
|
7
|
+
*/
|
|
8
|
+
import { redact } from "../debugLog.js";
|
|
9
|
+
import { isNetworkError } from "../networkError.js";
|
|
10
|
+
export const STATUS_CONNECTING = { state: "connecting", status: null, error: null };
|
|
11
|
+
/** The longest `statusError` a banner is handed. */
|
|
12
|
+
export const STATUS_ERROR_MAX = 140;
|
|
13
|
+
/**
|
|
14
|
+
* A failed check as one short line: scrubbed of secrets, the runtime tool
|
|
15
|
+
* name dropped, whitespace collapsed, cut at `STATUS_ERROR_MAX`.
|
|
16
|
+
*/
|
|
17
|
+
export function statusErrorMessage(error) {
|
|
18
|
+
const raw = error instanceof Error ? error.message : String(error ?? "");
|
|
19
|
+
const why = redact(raw)
|
|
20
|
+
.replace(/^[a-z0-9_]*service_status:\s*/i, "")
|
|
21
|
+
.replace(/\s+/g, " ")
|
|
22
|
+
.trim();
|
|
23
|
+
const line = isNetworkError(error)
|
|
24
|
+
? why
|
|
25
|
+
? `Could not reach the service — ${why}`
|
|
26
|
+
: "Could not reach the service"
|
|
27
|
+
: why || "The service did not answer";
|
|
28
|
+
return line.length > STATUS_ERROR_MAX ? `${line.slice(0, STATUS_ERROR_MAX - 1)}…` : line;
|
|
29
|
+
}
|
|
30
|
+
export function statusReducer(check, event) {
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case "start":
|
|
33
|
+
return { state: "connecting", status: check.status, error: null };
|
|
34
|
+
case "ok":
|
|
35
|
+
return { state: "ready", status: event.status, error: null };
|
|
36
|
+
case "fail":
|
|
37
|
+
return { state: "failed", status: check.status, error: statusErrorMessage(event.error) };
|
|
38
|
+
}
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tollbooth-dpyc/web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Shared browser SDK for Tollbooth DPYC operator front ends: npub sign-in, proofs, MCP client, profile and wallet",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Lonnie VanZandt",
|