@ofidj/entry 3.10.0 → 3.11.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 +150 -17
- package/dom.d.ts +27 -0
- package/dom.js +224 -0
- package/index.d.ts +2 -3
- package/index.js +15 -10
- package/model.d.ts +131 -0
- package/model.js +406 -0
- package/package.json +9 -1
- package/remembered.d.ts +3 -0
- package/remembered.js +33 -0
- package/window.d.ts +1 -0
- package/window.js +7 -0
- package/service-agreement.d.ts +0 -13
- package/service-agreement.js +0 -184
- package/version.d.ts +0 -1
- package/version.js +0 -22
package/README.md
CHANGED
|
@@ -26,10 +26,86 @@ renders, so there is one implementation and no hand-porting.
|
|
|
26
26
|
npm install @ofidj/entry
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
Three entry points, split by what they need to run:
|
|
30
|
+
|
|
31
|
+
| Import | Needs | What it holds |
|
|
32
|
+
| --------------------- | ---------------- | ------------------------------------------------------------------------------ |
|
|
33
|
+
| `@ofidj/entry` | nothing | **the model**: every rule, and every sentence a person is shown, as plain data |
|
|
34
|
+
| `@ofidj/entry/dom` | a document | one renderer of that model as HTML strings, and the helpers that drive it |
|
|
35
|
+
| `@ofidj/entry/window` | a browser window | the Fidj window and the answer it relays back |
|
|
36
|
+
|
|
37
|
+
The root is pure — no DOM, no markup, no view layer. That is deliberate: a
|
|
38
|
+
screen is a **value**, so the same entry can be drawn as HTML strings, as React
|
|
39
|
+
components or as Vue single-file components, and none of them re-derives a rule
|
|
40
|
+
or re-types a sentence.
|
|
41
|
+
|
|
42
|
+
### On React, Vue, Svelte, or a server
|
|
43
|
+
|
|
44
|
+
Take the model and render it yourself. Nothing here assumes a browser, so it
|
|
45
|
+
works under SSR, in a test, or in a plain Node script.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import {
|
|
49
|
+
agreementModel,
|
|
50
|
+
acceptance,
|
|
51
|
+
agreementRequired,
|
|
52
|
+
agreementFromRefusal,
|
|
53
|
+
verificationPending,
|
|
54
|
+
pollVerification,
|
|
55
|
+
signInErrorMessage,
|
|
56
|
+
} from "@ofidj/entry";
|
|
57
|
+
import { openProviderWindow } from "@ofidj/entry/window";
|
|
31
58
|
import "@ofidj/entry/tokens.css";
|
|
32
59
|
import "@ofidj/entry/style.css";
|
|
60
|
+
|
|
61
|
+
function Agreement({ title, agreement, onAccepted }) {
|
|
62
|
+
const [checked, setChecked] = useState(false);
|
|
63
|
+
const screen = agreementModel(title, agreement, { checked });
|
|
64
|
+
return (
|
|
65
|
+
<form
|
|
66
|
+
onSubmit={() =>
|
|
67
|
+
onAccepted(acceptance({ checked, version: screen.version }))
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
<h2>{screen.heading}</h2>
|
|
71
|
+
<p className="signin-lead">{screen.lead}</p>
|
|
72
|
+
<p className="fineprint">{screen.versionLabel}</p>
|
|
73
|
+
<div className="agreement-text" tabIndex={0}>
|
|
74
|
+
{screen.text}
|
|
75
|
+
</div>
|
|
76
|
+
<label className="agreement-choice">
|
|
77
|
+
<input
|
|
78
|
+
type="checkbox"
|
|
79
|
+
checked={checked}
|
|
80
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
81
|
+
/>
|
|
82
|
+
<span>{screen.checkboxLabel}</span>
|
|
83
|
+
</label>
|
|
84
|
+
<button className="primary" disabled={screen.submitDisabled}>
|
|
85
|
+
{screen.submitLabel}
|
|
86
|
+
</button>
|
|
87
|
+
</form>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`acceptance()` is the rule `@ofidj/entry/dom` applies to a checkbox, asked of
|
|
93
|
+
your own state instead: a tick is evidence only if it was live and only if it
|
|
94
|
+
names the version that was displayed. Both callers have to get the same answer,
|
|
95
|
+
or the evidence means different things depending on who collected it.
|
|
96
|
+
|
|
97
|
+
Authentication itself is not here — that is `@ofidj/node`. This package is the
|
|
98
|
+
screens and the flow around them.
|
|
99
|
+
|
|
100
|
+
### On a shell that builds pages out of strings
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { agreementRequired, agreementFromRefusal } from "@ofidj/entry";
|
|
104
|
+
import {
|
|
105
|
+
agreementScreen,
|
|
106
|
+
bindAgreementScreen,
|
|
107
|
+
acceptedAgreement,
|
|
108
|
+
} from "@ofidj/entry/dom";
|
|
33
109
|
```
|
|
34
110
|
|
|
35
111
|
`tokens.css` defines every colour, family and radius the product paints with;
|
|
@@ -39,24 +115,81 @@ prefix.
|
|
|
39
115
|
|
|
40
116
|
## What is here, and what is not
|
|
41
117
|
|
|
42
|
-
|
|
118
|
+
| Module | What it holds |
|
|
119
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `model.ts` | the refusals and what they mean, the acceptance rule, and a model per screen: entry, agreement, verification wait, account, credentials |
|
|
121
|
+
| `remembered.ts` | the address this browser remembers, and forgetting it |
|
|
122
|
+
| `dom.ts` | the HTML renderer, the agreement binding, the folding form, the `fidj@<version>` badge |
|
|
123
|
+
| `provider-window.ts` | the Fidj window and the answer it relays back |
|
|
124
|
+
| `tokens.css`, `fonts.css`, `style.css` | the design system |
|
|
125
|
+
|
|
126
|
+
Two tests hold the split, and they are the reason it stays true. One exercises
|
|
127
|
+
every model function in a file that never loads jsdom and never defines `window`
|
|
128
|
+
or `document`. The other asserts the HTML renderer says **what the model says**
|
|
129
|
+
and invents nothing — because a sentence able to drift between the two is a
|
|
130
|
+
React app and a generated app telling the same person different things, which is
|
|
131
|
+
the failure this package exists to end.
|
|
132
|
+
|
|
133
|
+
Still in `generator-fidj`'s template, and next to move: the Fidj-hosted OIDC
|
|
134
|
+
interaction page. It imports `app.config.json` directly, so it moves behind a
|
|
135
|
+
config argument rather than verbatim.
|
|
43
136
|
|
|
44
|
-
|
|
45
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
46
|
-
| `service-agreement.ts` | the agreement block and its loading, `providerEntry`'s three shapes, the remembered address, sign-in error wording |
|
|
47
|
-
| `provider-window.ts` | the Fidj window and the answer it relays back |
|
|
48
|
-
| `version.ts` | the `fidj@<version>` badge |
|
|
49
|
-
| `tokens.css`, `fonts.css`, `style.css` | the design system |
|
|
137
|
+
## The entry flow
|
|
50
138
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
139
|
+
[The workspace README](../README.md#entry-one-flow-the-same-everywhere) defines
|
|
140
|
+
it; this package implements it, so every surface gets the same one.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import {
|
|
144
|
+
pollVerification, // the wait ends by itself when the link is opened
|
|
145
|
+
agreementRequired, // was this refusal "you owe this app its agreement"?
|
|
146
|
+
agreementFromRefusal, // ...and the agreement it refused with
|
|
147
|
+
verificationPending, // was it "nobody has proved this address yet"?
|
|
148
|
+
credentialsModel, // email, password, two buttons, nothing gated
|
|
149
|
+
verificationWaitModel, // the wait a new account owes its address
|
|
150
|
+
agreementModel, // the second screen, and when its submit is live
|
|
151
|
+
} from "@ofidj/entry";
|
|
152
|
+
import {
|
|
153
|
+
credentialFields,
|
|
154
|
+
verificationWait,
|
|
155
|
+
agreementScreen,
|
|
156
|
+
bindAgreementScreen,
|
|
157
|
+
} from "@ofidj/entry/dom"; // ...the same screens, as markup
|
|
158
|
+
```
|
|
55
159
|
|
|
56
|
-
**The
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
160
|
+
**The API decides, not the screen.** Call `login`, and when it refuses with
|
|
161
|
+
`409 agreement_required`, show the agreement screen. A screen that decided for
|
|
162
|
+
itself
|
|
163
|
+
would have to know which version the account already accepted, which is the
|
|
164
|
+
API's to know — and an owner publishing a new version is what makes the
|
|
165
|
+
question owed again.
|
|
166
|
+
|
|
167
|
+
**Read the agreement off the refusal.** `agreementFromRefusal` returns the
|
|
168
|
+
version and text the API compared against, from `FidjError.details`. Fetching it
|
|
169
|
+
again with `GET /apps/:appId` usually gives the same answer and occasionally
|
|
170
|
+
not: an owner who publishes between the refusal and the refetch would have
|
|
171
|
+
somebody accept text the API is about to call stale. It needs `@ofidj/node`
|
|
172
|
+
3.10.2 or later, where `login` stopped rebuilding its error from
|
|
173
|
+
`err.toString()` and losing the body; against an older SDK it returns `null` and
|
|
174
|
+
the caller falls back to fetching.
|
|
175
|
+
|
|
176
|
+
**The wait is not a screen.** `verificationWait` returns a block to put _under_
|
|
177
|
+
the credential form, which stays where it is. That is what keeps a mistyped
|
|
178
|
+
address correctable: the field is still there, still filled in — correcting it
|
|
179
|
+
and creating the account again is the repair, and it reads as one because
|
|
180
|
+
nothing moved.
|
|
181
|
+
|
|
182
|
+
**`pollVerification` treats a failed check as no answer.** A browser that lost
|
|
183
|
+
its connection has learned nothing about the address, so the wait continues
|
|
184
|
+
rather than reporting anything.
|
|
185
|
+
|
|
186
|
+
### What went with the old arrangement
|
|
187
|
+
|
|
188
|
+
`agreementMarkup` and `bindAgreement` are gone. They put the checkbox beside the
|
|
189
|
+
credentials and gated the submit on whether the agreement had _loaded_, ticked or
|
|
190
|
+
not; the generated shells both render the two screens now, so nothing called
|
|
191
|
+
them. `agreementScreen` and `bindAgreementScreen` replace them, and the tick is
|
|
192
|
+
what opens the door.
|
|
60
193
|
|
|
61
194
|
## Release
|
|
62
195
|
|
package/dom.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type AccountState, type SigninShape, type VerificationState } from "./index.js";
|
|
2
|
+
export declare const escape: (value: unknown) => string;
|
|
3
|
+
export declare function masthead(logo: string, title: string): string;
|
|
4
|
+
export declare function highlightCells(entries?: Array<{
|
|
5
|
+
heading: string;
|
|
6
|
+
body: string;
|
|
7
|
+
}>): string;
|
|
8
|
+
export declare function badgeStrip(entries?: string[]): string;
|
|
9
|
+
export declare function credentialFields(state: {
|
|
10
|
+
email: string;
|
|
11
|
+
password: string;
|
|
12
|
+
}): string;
|
|
13
|
+
export declare function accountForm(route: string, state: AccountState): string;
|
|
14
|
+
export declare function returnNotice(asking: string): string;
|
|
15
|
+
export declare function providerEntry(title: string, appId: string, credentials: string, isFidjItself?: boolean, shape?: SigninShape): string;
|
|
16
|
+
export declare function agreementScreen(title: string, agreement: {
|
|
17
|
+
version?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
}): string;
|
|
20
|
+
export declare function verificationWait(state: VerificationState): string;
|
|
21
|
+
export declare function acceptedAgreement(form: HTMLFormElement): {
|
|
22
|
+
termsAccepted: boolean;
|
|
23
|
+
termsVersion: string;
|
|
24
|
+
} | null;
|
|
25
|
+
export declare function bindAgreementScreen(form: HTMLFormElement | null): void;
|
|
26
|
+
export declare function showEmailEntry(open: boolean, focus?: boolean): void;
|
|
27
|
+
export declare function showVersionBadge(version: string, apiEndpoint?: string): void;
|
package/dom.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// The entry, as markup and as behaviour.
|
|
2
|
+
//
|
|
3
|
+
// One renderer of the model in `@ofidj/entry`, for the shells that build pages
|
|
4
|
+
// out of strings. Every sentence on these screens comes from the model rather
|
|
5
|
+
// than from here — that is the property worth keeping, because it is what makes
|
|
6
|
+
// a React or Vue app render the *same* entry instead of a similar one.
|
|
7
|
+
//
|
|
8
|
+
// What stayed out of the model is the shell that holds the screens: the sign-in
|
|
9
|
+
// screen nests signin-trust inside its inner div and the interaction screen
|
|
10
|
+
// makes it a sibling, so a single shell builder would have had to change one of
|
|
11
|
+
// them. That is a decision, not a move.
|
|
12
|
+
import { accountModel, agreementModel, credentialsModel, providerEntryModel, returnNoticeModel, signInHint, verificationWaitModel, } from "./index.js";
|
|
13
|
+
export const escape = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
14
|
+
const attribute = (name, value) => value === undefined || value === false || value === ""
|
|
15
|
+
? ""
|
|
16
|
+
: ` ${name}="${escape(value)}"`;
|
|
17
|
+
const button = (control, extraClass = "") => {
|
|
18
|
+
const classes = [
|
|
19
|
+
control.kind === "primary"
|
|
20
|
+
? "primary"
|
|
21
|
+
: control.kind === "secondary"
|
|
22
|
+
? "secondary"
|
|
23
|
+
: "quiet",
|
|
24
|
+
extraClass,
|
|
25
|
+
]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join(" ");
|
|
28
|
+
// A control the entry's own form posts is a submit; one that only changes
|
|
29
|
+
// what is on screen is not, and saying so is what keeps a disclosure from
|
|
30
|
+
// opening a second window.
|
|
31
|
+
const type = control.name ? "submit" : "button";
|
|
32
|
+
return `<button class="${classes}" type="${type}"${attribute("id", control.name ? "" : control.id)}${attribute("name", control.name)}${attribute("value", control.value)}>${escape(control.label)}</button>`;
|
|
33
|
+
};
|
|
34
|
+
// The app's own mark beside its own name, decorative because the name is right
|
|
35
|
+
// there: every shell opens with this.
|
|
36
|
+
export function masthead(logo, title) {
|
|
37
|
+
return `<header class="signin-masthead"><img class="app-mark" src="${escape(logo)}" alt=""><strong>${escape(title)}</strong></header>`;
|
|
38
|
+
}
|
|
39
|
+
export function highlightCells(entries) {
|
|
40
|
+
if (!entries?.length)
|
|
41
|
+
return "";
|
|
42
|
+
return `<div class="signin-highlights">${entries
|
|
43
|
+
.map((entry, index) => `<article><p class="eyebrow">${String(index + 1).padStart(2, "0")}</p><h2>${escape(entry.heading)}</h2><p>${escape(entry.body)}</p></article>`)
|
|
44
|
+
.join("")}</div>`;
|
|
45
|
+
}
|
|
46
|
+
export function badgeStrip(entries) {
|
|
47
|
+
if (!entries?.length)
|
|
48
|
+
return "";
|
|
49
|
+
return `<footer class="signin-badges">${entries
|
|
50
|
+
.map((entry) => `<span>${escape(entry)}</span>`)
|
|
51
|
+
.join("")}</footer>`;
|
|
52
|
+
}
|
|
53
|
+
export function credentialFields(state) {
|
|
54
|
+
const model = credentialsModel(state);
|
|
55
|
+
return (`<label for="email">${escape(model.email.label)}</label><input id="email" type="email" value="${escape(model.email.value)}" placeholder="${escape(model.email.placeholder)}" autocomplete="username">` +
|
|
56
|
+
`<div class="field-head"><label for="password">${escape(model.password.label)}</label><a href="${escape(model.forgot.href)}">${escape(model.forgot.label)}</a></div>` +
|
|
57
|
+
`<div class="password-field"><input id="password" type="password" value="${escape(model.password.value)}" placeholder="${escape(model.password.placeholder)}" autocomplete="current-password"><button type="button" id="reveal" aria-controls="password">${escape(model.reveal.label)}</button></div>` +
|
|
58
|
+
`<button class="primary" type="submit" name="entry" value="credentials">${escape(model.submit.label)}</button>` +
|
|
59
|
+
`<button class="secondary" type="submit" name="signup" value="true">${escape(model.signup.label)}</button>`);
|
|
60
|
+
}
|
|
61
|
+
// The four account screens, drawn from the model that decides which of them has
|
|
62
|
+
// a form to show.
|
|
63
|
+
export function accountForm(route, state) {
|
|
64
|
+
const model = accountModel(route, state);
|
|
65
|
+
const fields = model.fields
|
|
66
|
+
.map((field) => `<label for="${escape(field.id)}">${escape(field.label)}</label><input id="${escape(field.id)}" type="${escape(field.type)}"${attribute("autocomplete", field.autocomplete)}${attribute("minlength", field.minlength)}${field.required ? " required" : ""}>`)
|
|
67
|
+
.join("");
|
|
68
|
+
const form = model.submitLabel
|
|
69
|
+
? `<form id="recovery">${fields}${model.hint ? `<p>${escape(model.hint)}</p>` : ""}<button class="primary">${escape(model.submitLabel)}</button></form>`
|
|
70
|
+
: "";
|
|
71
|
+
const alternative = model.alternative
|
|
72
|
+
? `${model.alternative.text ? `<p>${escape(model.alternative.text)}</p>` : ""}${model.alternative.href ? `<a href="${escape(model.alternative.href)}">${escape(model.alternative.label)}</a>` : ""}`
|
|
73
|
+
: "";
|
|
74
|
+
if (route === "forgot" || route === "reset" || route === "verify")
|
|
75
|
+
return `<h2>${escape(model.heading)}</h2><p>${escape(model.intro)}</p>${form}${alternative}`;
|
|
76
|
+
// My account keeps its own shape: an identity line, a status the page
|
|
77
|
+
// refreshes in place, and the controls that act on it.
|
|
78
|
+
return (`<h2>${escape(model.heading)}</h2>` +
|
|
79
|
+
`<p class="account-identity">Signed in as <strong>${escape(state.accountEmail)}</strong></p>` +
|
|
80
|
+
`<p>${escape(model.intro)}</p>` +
|
|
81
|
+
`<p id="verification-status">${escape(model.status)}</p>` +
|
|
82
|
+
model.extras
|
|
83
|
+
.map((control) => `<button id="${escape(control.id)}">${escape(control.label)}</button>`)
|
|
84
|
+
.join("") +
|
|
85
|
+
`<p><a href="${escape(model.alternative.href)}">${escape(model.alternative.label)}</a></p>`);
|
|
86
|
+
}
|
|
87
|
+
export function returnNotice(asking) {
|
|
88
|
+
return `<p class="signin-return" role="note">${escape(returnNoticeModel(asking))}</p>`;
|
|
89
|
+
}
|
|
90
|
+
// The entry every app that delegates to the provider renders. The shape logic
|
|
91
|
+
// lives in the model; what is here is the markup and the one browser fact the
|
|
92
|
+
// model refuses to reach for itself — what this browser remembers.
|
|
93
|
+
export function providerEntry(title, appId, credentials, isFidjItself = false, shape = "button") {
|
|
94
|
+
const model = providerEntryModel({
|
|
95
|
+
title,
|
|
96
|
+
hint: signInHint(appId),
|
|
97
|
+
isFidjItself,
|
|
98
|
+
shape,
|
|
99
|
+
hasCredentials: Boolean(credentials),
|
|
100
|
+
});
|
|
101
|
+
const lead = `<p class="signin-lead">${escape(model.lead)}</p>`;
|
|
102
|
+
if (!model.door)
|
|
103
|
+
return lead + credentials;
|
|
104
|
+
const door = button(model.door, "fidj-entry");
|
|
105
|
+
const forget = model.forget
|
|
106
|
+
? `<button type="button" id="${escape(model.forget.id)}" class="quiet">${escape(model.forget.label)}</button>`
|
|
107
|
+
: "";
|
|
108
|
+
if (!model.disclosure)
|
|
109
|
+
return lead + door + forget;
|
|
110
|
+
// Both doors. The app's own one is a second thought for the person who wants
|
|
111
|
+
// it, which is what a disclosure is for: it costs one click and takes nothing
|
|
112
|
+
// away, where a second button of equal weight cost everybody a decision.
|
|
113
|
+
return (lead +
|
|
114
|
+
door +
|
|
115
|
+
forget +
|
|
116
|
+
`<div class="signin-alternate"><button type="button" id="use-email" class="signin-toggle" aria-expanded="${model.disclosure.expanded}" aria-controls="${escape(model.disclosure.controls)}"><span class="signin-toggle-label">${escape(model.disclosure.label)}<span class="caret" aria-hidden="true"></span></span></button>
|
|
117
|
+
<div id="${escape(model.disclosure.controls)}" hidden>${credentials}</div></div>`);
|
|
118
|
+
}
|
|
119
|
+
// Screen three, drawn. The version travels on the checkbox so that what is
|
|
120
|
+
// recorded is what was displayed.
|
|
121
|
+
export function agreementScreen(title, agreement) {
|
|
122
|
+
const model = agreementModel(title, agreement);
|
|
123
|
+
return `<h2>${escape(model.heading)}</h2>
|
|
124
|
+
<p class="signin-lead">${escape(model.lead)}</p>
|
|
125
|
+
<p class="fineprint">${escape(model.versionLabel)}</p>
|
|
126
|
+
<div class="agreement-text" tabindex="0">${escape(model.text)}</div>
|
|
127
|
+
<label class="agreement-choice"><input id="service-agreement" type="checkbox" required aria-required="true" data-version="${escape(model.version)}"><span>${escape(model.checkboxLabel)}</span></label>
|
|
128
|
+
<button class="primary" type="submit"${model.submitDisabled ? " disabled" : ""}>${escape(model.submitLabel)}</button>`;
|
|
129
|
+
}
|
|
130
|
+
// Screen two, drawn under the form that started it.
|
|
131
|
+
export function verificationWait(state) {
|
|
132
|
+
const model = verificationWaitModel(state);
|
|
133
|
+
const notice = model.notice
|
|
134
|
+
? model.notice.kind === "error"
|
|
135
|
+
? `<p role="alert" class="error">${escape(model.notice.text)}</p>`
|
|
136
|
+
: `<p class="fineprint">${escape(model.notice.text)}</p>`
|
|
137
|
+
: "";
|
|
138
|
+
return `<div class="verification-wait">
|
|
139
|
+
<p role="status"><span class="spinner" aria-hidden="true"></span>${escape(model.status)} <strong>${escape(model.email)}</strong>.</p>
|
|
140
|
+
<p class="fineprint">${escape(model.fineprint)}</p>
|
|
141
|
+
${notice}
|
|
142
|
+
<button type="button" id="resend-verification">${escape(model.resendLabel)}</button>
|
|
143
|
+
</div>`;
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Reading and driving a real document
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// The acceptance, read off the form that collected it. The rule it applies is
|
|
149
|
+
// `acceptance()` in the model; what is here is where the three facts are found.
|
|
150
|
+
export function acceptedAgreement(form) {
|
|
151
|
+
const checkbox = form.querySelector("#service-agreement");
|
|
152
|
+
if (!checkbox?.checked || checkbox.disabled || !checkbox.dataset.version)
|
|
153
|
+
return null;
|
|
154
|
+
return { termsAccepted: true, termsVersion: checkbox.dataset.version };
|
|
155
|
+
}
|
|
156
|
+
// The tick is what opens the door, and unticking closes it again. This is the
|
|
157
|
+
// rule generator-fidj's README always claimed and its code never had:
|
|
158
|
+
// bindAgreement gated on whether the agreement had *loaded*, which is the right
|
|
159
|
+
// rule for a checkbox standing beside a password and the wrong one for a screen
|
|
160
|
+
// that exists only to collect it.
|
|
161
|
+
export function bindAgreementScreen(form) {
|
|
162
|
+
if (!form)
|
|
163
|
+
return;
|
|
164
|
+
const checkbox = form.querySelector("#service-agreement");
|
|
165
|
+
if (!checkbox)
|
|
166
|
+
return;
|
|
167
|
+
const submits = form.querySelectorAll('button[type="submit"]');
|
|
168
|
+
const update = () => submits.forEach((element) => {
|
|
169
|
+
element.disabled = !checkbox.checked;
|
|
170
|
+
});
|
|
171
|
+
checkbox.addEventListener("change", update);
|
|
172
|
+
update();
|
|
173
|
+
}
|
|
174
|
+
// Opening the app's own form takes the space the Fidj door was using.
|
|
175
|
+
//
|
|
176
|
+
// The two are alternatives, not a list, and watching one fold away as the other
|
|
177
|
+
// arrives is what says so — where a form appearing underneath a button that is
|
|
178
|
+
// still there reads as "and also". The toggle stays put through both states, so
|
|
179
|
+
// it ends up labelling whichever one is on screen: a caret pointing down at a
|
|
180
|
+
// form that is not here yet, and up at the one it would put away.
|
|
181
|
+
export function showEmailEntry(open, focus = false) {
|
|
182
|
+
const fields = document.getElementById("email-entry");
|
|
183
|
+
const toggle = document.getElementById("use-email");
|
|
184
|
+
if (!fields || !toggle)
|
|
185
|
+
return;
|
|
186
|
+
toggle.setAttribute("aria-expanded", String(open));
|
|
187
|
+
fields.hidden = !open;
|
|
188
|
+
const door = document.querySelector(".fidj-entry");
|
|
189
|
+
if (door) {
|
|
190
|
+
door.classList.toggle("is-folded", open);
|
|
191
|
+
// Out of the tab order and out of the accessibility tree the moment it
|
|
192
|
+
// starts leaving: something mid-fold is not something to press, and an
|
|
193
|
+
// animation is not what decides that.
|
|
194
|
+
if (open)
|
|
195
|
+
door.setAttribute("aria-hidden", "true");
|
|
196
|
+
else
|
|
197
|
+
door.removeAttribute("aria-hidden");
|
|
198
|
+
door.tabIndex = open ? -1 : 0;
|
|
199
|
+
}
|
|
200
|
+
if (open && focus)
|
|
201
|
+
document.getElementById("email")?.focus();
|
|
202
|
+
}
|
|
203
|
+
export function showVersionBadge(version, apiEndpoint) {
|
|
204
|
+
if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(version || ""))
|
|
205
|
+
return;
|
|
206
|
+
const badge = document.createElement("div");
|
|
207
|
+
badge.className = "fidj-version";
|
|
208
|
+
badge.setAttribute("aria-label", `Fidj version ${version}`);
|
|
209
|
+
badge.textContent = `fidj@${version}`;
|
|
210
|
+
document.body.append(badge);
|
|
211
|
+
if (apiEndpoint) {
|
|
212
|
+
void fetch(`${apiEndpoint.replace(/\/$/, "")}/status`)
|
|
213
|
+
.then((response) => (response.ok ? response.json() : null))
|
|
214
|
+
.then((status) => {
|
|
215
|
+
const apiVersion = status?.version || status?.built;
|
|
216
|
+
if (!apiVersion)
|
|
217
|
+
return;
|
|
218
|
+
badge.textContent = `fidj@${version} · API ${apiVersion}`;
|
|
219
|
+
badge.setAttribute("aria-label", `Fidj version ${version}, API version ${apiVersion}`);
|
|
220
|
+
})
|
|
221
|
+
.catch(() => undefined);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9tLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2RvbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx5Q0FBeUM7QUFDekMsRUFBRTtBQUNGLCtFQUErRTtBQUMvRSw4RUFBOEU7QUFDOUUsZ0ZBQWdGO0FBQ2hGLHVFQUF1RTtBQUN2RSxFQUFFO0FBQ0YsZ0ZBQWdGO0FBQ2hGLDRFQUE0RTtBQUM1RSxnRkFBZ0Y7QUFDaEYsd0NBQXdDO0FBRXhDLE9BQU8sRUFDTCxZQUFZLEVBQ1osY0FBYyxFQUNkLGdCQUFnQixFQUNoQixrQkFBa0IsRUFDbEIsaUJBQWlCLEVBQ2pCLFVBQVUsRUFDVixxQkFBcUIsR0FLdEIsTUFBTSxZQUFZLENBQUM7QUFFcEIsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYyxFQUFFLEVBQUUsQ0FDdkMsTUFBTSxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQ3pCLFVBQVUsRUFDVixDQUFDLElBQUksRUFBRSxFQUFFLENBQ1AsQ0FBQyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQ3ZFLElBQUksQ0FDSixDQUNMLENBQUM7QUFFSixNQUFNLFNBQVMsR0FBRyxDQUFDLElBQVksRUFBRSxLQUFpQyxFQUFFLEVBQUUsQ0FDcEUsS0FBSyxLQUFLLFNBQVMsSUFBSSxLQUFLLEtBQUssS0FBSyxJQUFJLEtBQUssS0FBSyxFQUFFO0lBQ3BELENBQUMsQ0FBQyxFQUFFO0lBQ0osQ0FBQyxDQUFDLElBQUksSUFBSSxLQUFLLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0FBRXBDLE1BQU0sTUFBTSxHQUFHLENBQUMsT0FBcUIsRUFBRSxVQUFVLEdBQUcsRUFBRSxFQUFFLEVBQUU7SUFDeEQsTUFBTSxPQUFPLEdBQUc7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
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { showVersionBadge } from "./version.js";
|
|
1
|
+
export { agreementRequired, agreementFromRefusal, verificationPending, signInErrorMessage, acceptance, providerEntryModel, agreementModel, verificationWaitModel, accountModel, credentialsModel, returnNoticeModel, pollVerification, type SigninShape, type EntryControl, type ProviderEntryModel, type AgreementModel, type VerificationState, type VerificationWaitModel, type AccountState, type AccountField, type AccountModel, type CredentialsModel, } from "./model.js";
|
|
2
|
+
export { signInHint, rememberSignIn, forgetSignIn } from "./remembered.js";
|
package/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
// The Fidj entry,
|
|
1
|
+
// The Fidj entry: the rules, and every sentence a person is shown.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
// This root is pure. No DOM, no markup, no view layer — a screen is a value, so
|
|
4
|
+
// the same entry can be drawn as HTML strings, as React components or as Vue
|
|
5
|
+
// single-file components without any of them re-deriving a rule or re-typing a
|
|
6
|
+
// sentence. That is what the package is for: these screens used to live only in
|
|
7
|
+
// generator-fidj's template, where shipping them meant generating an app, and
|
|
8
|
+
// the alternative — keeping a second copy in step by hand — is what produced
|
|
9
|
+
// four versions of the service agreement that no test could diff.
|
|
10
|
+
//
|
|
11
|
+
// Two subpaths carry what cannot be pure:
|
|
12
|
+
// @ofidj/entry/dom the HTML renderer and the helpers that drive a document
|
|
13
|
+
// @ofidj/entry/window the Fidj window and the answer it relays back
|
|
14
|
+
export { agreementRequired, agreementFromRefusal, verificationPending, signInErrorMessage, acceptance, providerEntryModel, agreementModel, verificationWaitModel, accountModel, credentialsModel, returnNoticeModel, pollVerification, } from "./model.js";
|
|
15
|
+
export { signInHint, rememberSignIn, forgetSignIn } from "./remembered.js";
|
|
16
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsbUVBQW1FO0FBQ25FLEVBQUU7QUFDRixnRkFBZ0Y7QUFDaEYsNkVBQTZFO0FBQzdFLCtFQUErRTtBQUMvRSxnRkFBZ0Y7QUFDaEYsOEVBQThFO0FBQzlFLDZFQUE2RTtBQUM3RSxrRUFBa0U7QUFDbEUsRUFBRTtBQUNGLDBDQUEwQztBQUMxQyxpRkFBaUY7QUFDakYsdUVBQXVFO0FBQ3ZFLE9BQU8sRUFDTCxpQkFBaUIsRUFDakIsb0JBQW9CLEVBQ3BCLG1CQUFtQixFQUNuQixrQkFBa0IsRUFDbEIsVUFBVSxFQUNWLGtCQUFrQixFQUNsQixjQUFjLEVBQ2QscUJBQXFCLEVBQ3JCLFlBQVksRUFDWixnQkFBZ0IsRUFDaEIsaUJBQWlCLEVBQ2pCLGdCQUFnQixHQVdqQixNQUFNLFlBQVksQ0FBQztBQUNwQixPQUFPLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQyJ9
|
package/model.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export type SigninShape = "button" | "inline" | "both";
|
|
2
|
+
export declare function agreementRequired(error: unknown): boolean;
|
|
3
|
+
export declare function agreementFromRefusal(error: unknown): {
|
|
4
|
+
version: string;
|
|
5
|
+
text: string;
|
|
6
|
+
} | null;
|
|
7
|
+
export declare function verificationPending(error: unknown): {
|
|
8
|
+
email: string;
|
|
9
|
+
} | null;
|
|
10
|
+
export declare function signInErrorMessage(error: unknown): "Too many attempts. Please wait before trying again." | "We could not sign in to this account. Check the email and password." | "An account already uses this email. Check the password or sign in instead." | "We cannot reach Fidj right now. Please try again." | "We could not sign in to this account. Please try again.";
|
|
11
|
+
export declare function acceptance(state: {
|
|
12
|
+
checked?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
version?: string;
|
|
15
|
+
}): {
|
|
16
|
+
termsAccepted: true;
|
|
17
|
+
termsVersion: string;
|
|
18
|
+
} | null;
|
|
19
|
+
export type EntryControl = {
|
|
20
|
+
label: string;
|
|
21
|
+
id: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
value?: string;
|
|
24
|
+
kind: "primary" | "secondary" | "quiet";
|
|
25
|
+
};
|
|
26
|
+
export type ProviderEntryModel = {
|
|
27
|
+
shape: SigninShape;
|
|
28
|
+
lead: string;
|
|
29
|
+
door: EntryControl | null;
|
|
30
|
+
forget: EntryControl | null;
|
|
31
|
+
disclosure: {
|
|
32
|
+
label: string;
|
|
33
|
+
controls: string;
|
|
34
|
+
expanded: boolean;
|
|
35
|
+
} | null;
|
|
36
|
+
};
|
|
37
|
+
export declare function providerEntryModel(options: {
|
|
38
|
+
title: string;
|
|
39
|
+
hint?: string;
|
|
40
|
+
isFidjItself?: boolean;
|
|
41
|
+
shape?: SigninShape;
|
|
42
|
+
hasCredentials?: boolean;
|
|
43
|
+
}): ProviderEntryModel;
|
|
44
|
+
export type AgreementModel = {
|
|
45
|
+
heading: string;
|
|
46
|
+
lead: string;
|
|
47
|
+
versionLabel: string;
|
|
48
|
+
version: string;
|
|
49
|
+
text: string;
|
|
50
|
+
checkboxLabel: string;
|
|
51
|
+
submitLabel: string;
|
|
52
|
+
submitDisabled: boolean;
|
|
53
|
+
};
|
|
54
|
+
export declare function agreementModel(title: string, agreement: {
|
|
55
|
+
version?: string;
|
|
56
|
+
text?: string;
|
|
57
|
+
}, state?: {
|
|
58
|
+
checked?: boolean;
|
|
59
|
+
}): AgreementModel;
|
|
60
|
+
export type VerificationState = {
|
|
61
|
+
email: string;
|
|
62
|
+
resent?: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
};
|
|
65
|
+
export type VerificationWaitModel = {
|
|
66
|
+
status: string;
|
|
67
|
+
email: string;
|
|
68
|
+
fineprint: string;
|
|
69
|
+
notice: {
|
|
70
|
+
kind: "error" | "info";
|
|
71
|
+
text: string;
|
|
72
|
+
} | null;
|
|
73
|
+
resendLabel: string;
|
|
74
|
+
};
|
|
75
|
+
export declare function verificationWaitModel(state: VerificationState): VerificationWaitModel;
|
|
76
|
+
export type AccountState = {
|
|
77
|
+
linkToken?: string;
|
|
78
|
+
verificationConfirmed?: boolean;
|
|
79
|
+
emailVerified?: boolean;
|
|
80
|
+
accountEmail?: string;
|
|
81
|
+
};
|
|
82
|
+
export type AccountField = {
|
|
83
|
+
id: string;
|
|
84
|
+
label: string;
|
|
85
|
+
type: string;
|
|
86
|
+
autocomplete?: string;
|
|
87
|
+
minlength?: number;
|
|
88
|
+
required?: boolean;
|
|
89
|
+
};
|
|
90
|
+
export type AccountModel = {
|
|
91
|
+
heading: string;
|
|
92
|
+
intro: string;
|
|
93
|
+
fields: AccountField[];
|
|
94
|
+
hint: string;
|
|
95
|
+
submitLabel: string;
|
|
96
|
+
alternative: {
|
|
97
|
+
text: string;
|
|
98
|
+
href?: string;
|
|
99
|
+
label?: string;
|
|
100
|
+
} | null;
|
|
101
|
+
status: string;
|
|
102
|
+
extras: EntryControl[];
|
|
103
|
+
};
|
|
104
|
+
export declare function accountModel(route: string, state: AccountState): AccountModel;
|
|
105
|
+
export type CredentialsModel = {
|
|
106
|
+
email: AccountField & {
|
|
107
|
+
value: string;
|
|
108
|
+
placeholder: string;
|
|
109
|
+
};
|
|
110
|
+
password: AccountField & {
|
|
111
|
+
value: string;
|
|
112
|
+
placeholder: string;
|
|
113
|
+
};
|
|
114
|
+
forgot: {
|
|
115
|
+
href: string;
|
|
116
|
+
label: string;
|
|
117
|
+
};
|
|
118
|
+
reveal: EntryControl;
|
|
119
|
+
submit: EntryControl;
|
|
120
|
+
signup: EntryControl;
|
|
121
|
+
};
|
|
122
|
+
export declare function credentialsModel(state: {
|
|
123
|
+
email: string;
|
|
124
|
+
password: string;
|
|
125
|
+
}): CredentialsModel;
|
|
126
|
+
export declare function returnNoticeModel(asking: string): string;
|
|
127
|
+
export declare function pollVerification(options: {
|
|
128
|
+
check: () => Promise<boolean>;
|
|
129
|
+
onVerified: () => void;
|
|
130
|
+
intervalMs?: number;
|
|
131
|
+
}): () => void;
|