@ofidj/entry 3.10.1 → 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/model.js ADDED
@@ -0,0 +1,406 @@
1
+ // The entry, as data.
2
+ //
3
+ // Everything a person is told and every rule deciding what they are told, with
4
+ // no markup and no DOM. A screen is a value here: the copy, the state, and
5
+ // which controls are live. Rendering it is somebody else's business —
6
+ // `@ofidj/entry/dom` renders it as HTML strings, and a React or Vue app renders
7
+ // the same value as components without re-deriving a single sentence.
8
+ //
9
+ // That split is the point. The wording and the rules are what took the arguing;
10
+ // a `<label>` is not. When the two lived together the only way to reuse either
11
+ // was to accept both, so an app on another view layer had to copy the sentences
12
+ // out — which is how the service agreement ended up existing in four versions
13
+ // that no test could diff.
14
+ // ---------------------------------------------------------------------------
15
+ // What a refusal means
16
+ // ---------------------------------------------------------------------------
17
+ const AGREEMENT_REQUIRED = "agreement_required";
18
+ // Whether this refusal means the person owes this app its agreement.
19
+ //
20
+ // The API answers 409 with `code: agreement_required` and the agreement itself,
21
+ // but the SDK's FidjError carries a code and a reason and has no room for a
22
+ // body — so the code arrives as a number, as a name, or as the whole body
23
+ // squeezed into a string, depending on which call refused. All three mean the
24
+ // same thing, and guessing wrong costs a person the screen that lets them in.
25
+ export function agreementRequired(error) {
26
+ if (!error || typeof error !== "object")
27
+ return false;
28
+ const detail = error;
29
+ if (detail.code === 409)
30
+ return true;
31
+ for (const field of [detail.code, detail.reason, detail.message]) {
32
+ if (typeof field === "string" && field.includes(AGREEMENT_REQUIRED))
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+ // The agreement the refusal carried, which is the one the API compared against.
38
+ //
39
+ // Fetching it again would usually give the same answer and occasionally not:
40
+ // an owner who publishes a new version between the refusal and the refetch
41
+ // would have the person accept text the API is about to call stale. Both halves
42
+ // are required — a version with no text is nothing to read, and text with no
43
+ // version is nothing to record.
44
+ export function agreementFromRefusal(error) {
45
+ if (!agreementRequired(error))
46
+ return null;
47
+ const details = error.details;
48
+ const agreement = details?.agreement;
49
+ if (typeof agreement?.version !== "string" || !agreement.version)
50
+ return null;
51
+ if (typeof agreement?.text !== "string" || !agreement.text)
52
+ return null;
53
+ return { version: agreement.version, text: agreement.text };
54
+ }
55
+ // What the SDK says when POST /v3/users answered 201: the account was created
56
+ // by this very call, so there is no session and the link is what will make one.
57
+ //
58
+ // It returns the address rather than a boolean because the wait has to name it,
59
+ // and the address the account was created with is the one the link went to —
60
+ // not whatever is in the field by the time this is read.
61
+ export function verificationPending(error) {
62
+ if (!error || typeof error !== "object")
63
+ return null;
64
+ const detail = error;
65
+ if (detail.reason !== "verification-required")
66
+ return null;
67
+ const email = detail.details?.email;
68
+ return { email: typeof email === "string" ? email : "" };
69
+ }
70
+ export function signInErrorMessage(error) {
71
+ const detail = error;
72
+ const reason = typeof detail?.reason === "string"
73
+ ? detail.reason
74
+ : typeof detail?.message === "string"
75
+ ? detail.message
76
+ : "";
77
+ if (detail?.code === 429)
78
+ return "Too many attempts. Please wait before trying again.";
79
+ if (reason === "unknown-user")
80
+ return "We could not sign in to this account. Check the email and password.";
81
+ if (reason === "already exists - inconsistent request")
82
+ return "An account already uses this email. Check the password or sign in instead.";
83
+ if (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|EHOSTUNREACH|network/i.test(reason))
84
+ return "We cannot reach Fidj right now. Please try again.";
85
+ return "We could not sign in to this account. Please try again.";
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // What an acceptance is worth
89
+ // ---------------------------------------------------------------------------
90
+ // The rule, with nothing to read it off.
91
+ //
92
+ // A tick is evidence only if it was live and only if it names a version: the
93
+ // version travels with the checkbox so that what is recorded is what was
94
+ // displayed, and acceptance of a version nobody was shown is not evidence.
95
+ // `@ofidj/entry/dom` reads these three facts off a form element; a React app
96
+ // reads them off its own state and asks the same question here.
97
+ export function acceptance(state) {
98
+ if (!state.checked || state.disabled || !state.version)
99
+ return null;
100
+ return { termsAccepted: true, termsVersion: state.version };
101
+ }
102
+ // Both doors, but not side by side.
103
+ //
104
+ // An app that delegates to Fidj still has people who would rather type an
105
+ // address and a password than be sent somewhere, so the credential form stays —
106
+ // folded away under a line of text rather than standing in front of the door
107
+ // most people should take. Offering the two as equals asked every arrival to
108
+ // choose between them, and the one that looked like an ordinary login won by
109
+ // looking ordinary.
110
+ //
111
+ // "Continue with Fidj" alone borrows the grammar of an optional social login —
112
+ // that button always sits next to an email and a password — so on an app whose
113
+ // accounts *are* Fidj accounts, the missing form reads as something broken. The
114
+ // explanation therefore comes before the button, not as reassurance after it,
115
+ // and nothing presupposes an account the person may not have yet.
116
+ //
117
+ // `hint` is passed in rather than read: remembering is a browser fact, and a
118
+ // model that reaches for `localStorage` is a model that cannot be rendered on a
119
+ // server or tested without one.
120
+ export function providerEntryModel(options) {
121
+ const { title, hint = "", isFidjItself = false, shape = "button", hasCredentials = false, } = options;
122
+ const both = shape === "both" && hasCredentials;
123
+ // The app's own form and nothing else. Nothing is folded away because there
124
+ // is no second way in to fold it under, and the trade-off is stated plainly:
125
+ // on this path it is this site that holds the password.
126
+ if (shape === "inline") {
127
+ return {
128
+ shape,
129
+ lead: isFidjItself
130
+ ? "One account across every app that uses Fidj, and a separate set of choices for each one."
131
+ : `${title} accounts are Fidj accounts. Sign in below — ${title} handles your password itself on this page.`,
132
+ door: null,
133
+ forget: null,
134
+ disclosure: null,
135
+ };
136
+ }
137
+ const lead = hint
138
+ ? `You signed in here with Fidj before. ${title} accounts are Fidj accounts — continue as yourself, or use another.`
139
+ : both
140
+ ? `${title} accounts are Fidj accounts. Fidj asks in a window of its own, so this site never sees your password.`
141
+ : isFidjItself
142
+ ? "One account across every app that uses Fidj, and a separate set of choices for each one. Fidj asks in a window of its own; this page stays where it is."
143
+ : `${title} accounts are Fidj accounts. You will sign in — or create yours — in a Fidj window, so this site never sees your password.`;
144
+ // The accent is the Fidj mark, and this is the one control on the screen that
145
+ // is Fidj's rather than the app's. It is also the door the app would rather
146
+ // people took, and those two happen to want the same thing.
147
+ const door = {
148
+ id: "fidj-entry",
149
+ name: "entry",
150
+ value: "fidj",
151
+ kind: "primary",
152
+ label: hint
153
+ ? `Continue as ${hint}`
154
+ : isFidjItself
155
+ ? "Sign in"
156
+ : "Sign in with Fidj",
157
+ };
158
+ const forget = hint
159
+ ? { id: "forget-hint", kind: "quiet", label: "Use a different account" }
160
+ : null;
161
+ return {
162
+ shape,
163
+ lead,
164
+ door,
165
+ forget,
166
+ // Fidj is the only door: it collects the agreement itself, a moment later,
167
+ // on the screen that names the app — and records it with its version.
168
+ disclosure: both
169
+ ? { label: "Inline form", controls: "email-entry", expanded: false }
170
+ : null,
171
+ };
172
+ }
173
+ // Screen three. The text and the version come from the app's own agreement, and
174
+ // the version travels with the acceptance so that what is recorded is what was
175
+ // displayed.
176
+ export function agreementModel(title, agreement, state = {}) {
177
+ return {
178
+ heading: "Before you continue",
179
+ lead: `${title} asks you to accept its service agreement. Your optional privacy choices stay separate, and you can change them at any time.`,
180
+ versionLabel: `Version ${agreement.version ?? ""}`,
181
+ version: String(agreement.version ?? ""),
182
+ text: String(agreement.text ?? ""),
183
+ checkboxLabel: `I accept the service agreement for ${title}.`,
184
+ submitLabel: "Sign in",
185
+ submitDisabled: !state.checked,
186
+ };
187
+ }
188
+ // The wait, underneath the form that started it.
189
+ //
190
+ // Creating an account never signs anybody in: the account exists, it has no
191
+ // session, and the link is what finishes the sign-in. But that is not a place
192
+ // somebody was taken to, so it does not get a screen — it appears below the
193
+ // credentials they just submitted, which stay where they are.
194
+ //
195
+ // That is also what makes a mistyped address recoverable without a control of
196
+ // its own: the address field is still on screen, still filled in, still
197
+ // editable. Correcting it and pressing Create an account again is the repair,
198
+ // and it reads as one because nothing moved.
199
+ export function verificationWaitModel(state) {
200
+ return {
201
+ status: "Your account is created. Waiting for you to open the link sent to",
202
+ email: state.email,
203
+ fineprint: "This continues on its own once you have. The link may take a minute, and it sometimes lands in spam. Wrong address? Correct it above and create the account again.",
204
+ notice: state.error
205
+ ? { kind: "error", text: state.error }
206
+ : state.resent
207
+ ? {
208
+ kind: "info",
209
+ text: "The link was sent again. Only the newest one works.",
210
+ }
211
+ : null,
212
+ resendLabel: "Send the link again",
213
+ };
214
+ }
215
+ // The four account screens. My account is shown inside the app; the recovery
216
+ // and verification ones are reached without a session and stand alone.
217
+ export function accountModel(route, state) {
218
+ const empty = {
219
+ heading: "",
220
+ intro: "",
221
+ fields: [],
222
+ hint: "",
223
+ submitLabel: "",
224
+ alternative: null,
225
+ status: "",
226
+ extras: [],
227
+ };
228
+ if (route === "forgot")
229
+ return {
230
+ ...empty,
231
+ heading: "Reset your password",
232
+ intro: "We’ll email you a link to choose a new password for your shared Fidj account.",
233
+ fields: [
234
+ {
235
+ id: "recovery-email",
236
+ label: "Email address",
237
+ type: "email",
238
+ autocomplete: "email",
239
+ required: true,
240
+ },
241
+ ],
242
+ submitLabel: "Send reset link",
243
+ };
244
+ if (route === "reset")
245
+ return {
246
+ ...empty,
247
+ heading: "Choose a new password",
248
+ intro: "This changes your Fidj password across all your apps and signs out existing sessions.",
249
+ fields: state.linkToken
250
+ ? [
251
+ {
252
+ id: "new-password",
253
+ label: "New password",
254
+ type: "password",
255
+ autocomplete: "new-password",
256
+ minlength: 12,
257
+ required: true,
258
+ },
259
+ {
260
+ id: "confirm-password",
261
+ label: "Confirm password",
262
+ type: "password",
263
+ autocomplete: "new-password",
264
+ minlength: 12,
265
+ required: true,
266
+ },
267
+ ]
268
+ : [],
269
+ hint: state.linkToken
270
+ ? "Use at least 12 characters (up to 72 UTF-8 bytes)."
271
+ : "",
272
+ submitLabel: state.linkToken ? "Save new password" : "",
273
+ alternative: state.linkToken
274
+ ? null
275
+ : {
276
+ text: "Request a new link if you no longer have an active reset link.",
277
+ href: "#/forgot",
278
+ label: "Request a reset link",
279
+ },
280
+ };
281
+ if (route === "verify")
282
+ return {
283
+ ...empty,
284
+ heading: state.verificationConfirmed
285
+ ? "Email verified"
286
+ : "Verify your email",
287
+ intro: state.verificationConfirmed
288
+ ? "Your account is ready. Return to your app to continue."
289
+ : "Confirm that this email address belongs to you.",
290
+ submitLabel: !state.verificationConfirmed && state.linkToken
291
+ ? "Confirm email address"
292
+ : "",
293
+ alternative: !state.verificationConfirmed && !state.linkToken
294
+ ? {
295
+ text: "Sign in to your account to request a new verification email.",
296
+ }
297
+ : null,
298
+ };
299
+ return {
300
+ ...empty,
301
+ heading: "My Fidj account",
302
+ intro: "Your identity is shared across your apps. Privacy choices remain separate for each app.",
303
+ status: state.emailVerified
304
+ ? "Your email address is verified."
305
+ : "Your email is not verified yet.",
306
+ alternative: { text: "", href: "#/forgot", label: "Reset my password" },
307
+ extras: [
308
+ {
309
+ id: "check-verification",
310
+ kind: "secondary",
311
+ label: "Refresh verification status",
312
+ },
313
+ ...(state.emailVerified
314
+ ? []
315
+ : [
316
+ {
317
+ id: "resend-verification",
318
+ kind: "secondary",
319
+ label: "Send verification email",
320
+ },
321
+ ]),
322
+ ],
323
+ };
324
+ }
325
+ // Both doors, and what was already typed. The entry is rebuilt on every render
326
+ // and a refused sign-in is a render, so the values come back in rather than
327
+ // being lost with the markup that held them.
328
+ export function credentialsModel(state) {
329
+ return {
330
+ email: {
331
+ id: "email",
332
+ label: "Email",
333
+ type: "email",
334
+ autocomplete: "username",
335
+ value: state.email,
336
+ placeholder: "you@company.com",
337
+ },
338
+ password: {
339
+ id: "password",
340
+ label: "Password",
341
+ type: "password",
342
+ autocomplete: "current-password",
343
+ value: state.password,
344
+ placeholder: "••••••••••",
345
+ },
346
+ forgot: { href: "#/forgot", label: "Forgot?" },
347
+ reveal: { id: "reveal", kind: "quiet", label: "Show" },
348
+ submit: {
349
+ id: "entry-credentials",
350
+ name: "entry",
351
+ value: "credentials",
352
+ kind: "primary",
353
+ label: "Continue",
354
+ },
355
+ signup: {
356
+ id: "entry-signup",
357
+ name: "signup",
358
+ value: "true",
359
+ kind: "secondary",
360
+ label: "Create an account",
361
+ },
362
+ };
363
+ }
364
+ // A window that opened on its own, over the page somebody was on, owes them the
365
+ // way out before it asks for anything. Naming the app they came from is also the
366
+ // only thing on this screen that they can check against what they were doing a
367
+ // second ago — which is exactly what a page asking for a password should offer.
368
+ export function returnNoticeModel(asking) {
369
+ return `When you are done, this window closes and takes you back to ${asking}.`;
370
+ }
371
+ // ---------------------------------------------------------------------------
372
+ // The wait resolves by itself
373
+ // ---------------------------------------------------------------------------
374
+ // Screen two resolves by itself, which means asking. A failed check is not an
375
+ // answer: a browser that lost its connection has learned nothing about the
376
+ // address, so it keeps waiting rather than reporting anything.
377
+ export function pollVerification(options) {
378
+ const interval = options.intervalMs ?? 4000;
379
+ let stopped = false;
380
+ let timer;
381
+ const stop = () => {
382
+ stopped = true;
383
+ clearTimeout(timer);
384
+ };
385
+ const ask = async () => {
386
+ if (stopped)
387
+ return;
388
+ let verified = false;
389
+ try {
390
+ verified = await options.check();
391
+ }
392
+ catch {
393
+ // Nothing was learned. Wait, and ask again.
394
+ }
395
+ if (stopped)
396
+ return;
397
+ if (verified) {
398
+ options.onVerified();
399
+ return;
400
+ }
401
+ timer = setTimeout(ask, interval);
402
+ };
403
+ timer = setTimeout(ask, interval);
404
+ return stop;
405
+ }
406
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsc0JBQXNCO0FBQ3RCLEVBQUU7QUFDRiwrRUFBK0U7QUFDL0UsMkVBQTJFO0FBQzNFLHNFQUFzRTtBQUN0RSxnRkFBZ0Y7QUFDaEYsc0VBQXNFO0FBQ3RFLEVBQUU7QUFDRixnRkFBZ0Y7QUFDaEYsK0VBQStFO0FBQy9FLGdGQUFnRjtBQUNoRiw4RUFBOEU7QUFDOUUsMkJBQTJCO0FBSTNCLDhFQUE4RTtBQUM5RSx1QkFBdUI7QUFDdkIsOEVBQThFO0FBRTlFLE1BQU0sa0JBQWtCLEdBQUcsb0JBQW9CLENBQUM7QUFFaEQscUVBQXFFO0FBQ3JFLEVBQUU7QUFDRixnRkFBZ0Y7QUFDaEYsNEVBQTRFO0FBQzVFLDBFQUEwRTtBQUMxRSw4RUFBOEU7QUFDOUUsOEVBQThFO0FBQzlFLE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxLQUFjO0lBQzlDLElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3RELE1BQU0sTUFBTSxHQUFHLEtBSWQsQ0FBQztJQUNGLElBQUksTUFBTSxDQUFDLElBQUksS0FBSyxHQUFHO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDckMsS0FBSyxNQUFNLEtBQUssSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNqRSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDO1lBQ2pFLE9BQU8sSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFFRCxnRkFBZ0Y7QUFDaEYsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSwyRUFBMkU7QUFDM0UsZ0ZBQWdGO0FBQ2hGLDZFQUE2RTtBQUM3RSxnQ0FBZ0M7QUFDaEMsTUFBTSxVQUFVLG9CQUFvQixDQUNsQyxLQUFjO0lBRWQsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQzNDLE1BQU0sT0FBTyxHQUFJLEtBQStCLENBQUMsT0FDa0IsQ0FBQztJQUNwRSxNQUFNLFNBQVMsR0FBRyxPQUFPLEVBQUUsU0FBUyxDQUFDO0lBQ3JDLElBQUksT0FBTyxTQUFTLEVBQUUsT0FBTyxLQUFLLFFBQVEsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDOUUsSUFBSSxPQUFPLFNBQVMsRUFBRSxJQUFJLEtBQUssUUFBUSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUk7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN4RSxPQUFPLEVBQUUsT0FBTyxFQUFFLFNBQVMsQ0FBQyxPQUFPLEVBQUUsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUM5RCxDQUFDO0FBRUQsOEVBQThFO0FBQzlFLGdGQUFnRjtBQUNoRixFQUFFO0FBQ0YsZ0ZBQWdGO0FBQ2hGLDZFQUE2RTtBQUM3RSx5REFBeUQ7QUFDekQsTUFBTSxVQUFVLG1CQUFtQixDQUFDLEtBQWM7SUFDaEQsSUFBSSxDQUFDLEtBQUssSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDckQsTUFBTSxNQUFNLEdBQUcsS0FBNEQsQ0FBQztJQUM1RSxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssdUJBQXVCO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDM0QsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUM7SUFDcEMsT0FBTyxFQUFFLEtBQUssRUFBRSxPQUFPLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUM7QUFDM0QsQ0FBQztBQUVELE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxLQUFjO0lBQy9DLE1BQU0sTUFBTSxHQUFHLEtBSWQsQ0FBQztJQUNGLE1BQU0sTUFBTSxHQUNWLE9BQU8sTUFBTSxFQUFFLE1BQU0sS0FBSyxRQUFRO1FBQ2hDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTTtRQUNmLENBQUMsQ0FBQyxPQUFPLE1BQU0sRUFBRSxPQUFPLEtBQUssUUFBUTtZQUNuQyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU87WUFDaEIsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNYLElBQUksTUFBTSxFQUFFLElBQUksS0FBSyxHQUFHO1FBQ3RCLE9BQU8scURBQXFELENBQUM7SUFDL0QsSUFBSSxNQUFNLEtBQUssY0FBYztRQUMzQixPQUFPLHFFQUFxRSxDQUFDO0lBQy9FLElBQUksTUFBTSxLQUFLLHVDQUF1QztRQUNwRCxPQUFPLDRFQUE0RSxDQUFDO0lBQ3RGLElBQ0UsbUVBQW1FLENBQUMsSUFBSSxDQUN0RSxNQUFNLENBQ1A7UUFFRCxPQUFPLG1EQUFtRCxDQUFDO0lBQzdELE9BQU8seURBQXlELENBQUM7QUFDbkUsQ0FBQztBQUVELDhFQUE4RTtBQUM5RSw4QkFBOEI7QUFDOUIsOEVBQThFO0FBRTlFLHlDQUF5QztBQUN6QyxFQUFFO0FBQ0YsNkVBQTZFO0FBQzdFLHlFQUF5RTtBQUN6RSwyRUFBMkU7QUFDM0UsNkVBQTZFO0FBQzdFLGdFQUFnRTtBQUNoRSxNQUFNLFVBQVUsVUFBVSxDQUFDLEtBSTFCO0lBQ0MsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLElBQUksS0FBSyxDQUFDLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDcEUsT0FBTyxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQztBQUM5RCxDQUFDO0FBNEJELG9DQUFvQztBQUNwQyxFQUFFO0FBQ0YsMEVBQTBFO0FBQzFFLGdGQUFnRjtBQUNoRiw2RUFBNkU7QUFDN0UsNkVBQTZFO0FBQzdFLDZFQUE2RTtBQUM3RSxvQkFBb0I7QUFDcEIsRUFBRTtBQUNGLCtFQUErRTtBQUMvRSwrRUFBK0U7QUFDL0UsZ0ZBQWdGO0FBQ2hGLDhFQUE4RTtBQUM5RSxrRUFBa0U7QUFDbEUsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSxnRkFBZ0Y7QUFDaEYsZ0NBQWdDO0FBQ2hDLE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxPQU1sQztJQUNDLE1BQU0sRUFDSixLQUFLLEVBQ0wsSUFBSSxHQUFHLEVBQUUsRUFDVCxZQUFZLEdBQUcsS0FBSyxFQUNwQixLQUFLLEdBQUcsUUFBUSxFQUNoQixjQUFjLEdBQUcsS0FBSyxHQUN2QixHQUFHLE9BQU8sQ0FBQztJQUNaLE1BQU0sSUFBSSxHQUFHLEtBQUssS0FBSyxNQUFNLElBQUksY0FBYyxDQUFDO0lBRWhELDRFQUE0RTtJQUM1RSw2RUFBNkU7SUFDN0Usd0RBQXdEO0lBQ3hELElBQUksS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3ZCLE9BQU87WUFDTCxLQUFLO1lBQ0wsSUFBSSxFQUFFLFlBQVk7Z0JBQ2hCLENBQUMsQ0FBQywwRkFBMEY7Z0JBQzVGLENBQUMsQ0FBQyxHQUFHLEtBQUssZ0RBQWdELEtBQUssNkNBQTZDO1lBQzlHLElBQUksRUFBRSxJQUFJO1lBQ1YsTUFBTSxFQUFFLElBQUk7WUFDWixVQUFVLEVBQUUsSUFBSTtTQUNqQixDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sSUFBSSxHQUFHLElBQUk7UUFDZixDQUFDLENBQUMsd0NBQXdDLEtBQUsscUVBQXFFO1FBQ3BILENBQUMsQ0FBQyxJQUFJO1lBQ0osQ0FBQyxDQUFDLEdBQUcsS0FBSyx1R0FBdUc7WUFDakgsQ0FBQyxDQUFDLFlBQVk7Z0JBQ1osQ0FBQyxDQUFDLHlKQUF5SjtnQkFDM0osQ0FBQyxDQUFDLEdBQUcsS0FBSyw0SEFBNEgsQ0FBQztJQUU3SSw4RUFBOEU7SUFDOUUsNEVBQTRFO0lBQzVFLDREQUE0RDtJQUM1RCxNQUFNLElBQUksR0FBaUI7UUFDekIsRUFBRSxFQUFFLFlBQVk7UUFDaEIsSUFBSSxFQUFFLE9BQU87UUFDYixLQUFLLEVBQUUsTUFBTTtRQUNiLElBQUksRUFBRSxTQUFTO1FBQ2YsS0FBSyxFQUFFLElBQUk7WUFDVCxDQUFDLENBQUMsZUFBZSxJQUFJLEVBQUU7WUFDdkIsQ0FBQyxDQUFDLFlBQVk7Z0JBQ1osQ0FBQyxDQUFDLFNBQVM7Z0JBQ1gsQ0FBQyxDQUFDLG1CQUFtQjtLQUMxQixDQUFDO0lBQ0YsTUFBTSxNQUFNLEdBQXdCLElBQUk7UUFDdEMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSx5QkFBeUIsRUFBRTtRQUN4RSxDQUFDLENBQUMsSUFBSSxDQUFDO0lBRVQsT0FBTztRQUNMLEtBQUs7UUFDTCxJQUFJO1FBQ0osSUFBSTtRQUNKLE1BQU07UUFDTiwyRUFBMkU7UUFDM0Usc0VBQXNFO1FBQ3RFLFVBQVUsRUFBRSxJQUFJO1lBQ2QsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLGFBQWEsRUFBRSxRQUFRLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUU7WUFDcEUsQ0FBQyxDQUFDLElBQUk7S0FDVCxDQUFDO0FBQ0osQ0FBQztBQWdCRCxnRkFBZ0Y7QUFDaEYsK0VBQStFO0FBQy9FLGFBQWE7QUFDYixNQUFNLFVBQVUsY0FBYyxDQUM1QixLQUFhLEVBQ2IsU0FBOEMsRUFDOUMsUUFBK0IsRUFBRTtJQUVqQyxPQUFPO1FBQ0wsT0FBTyxFQUFFLHFCQUFxQjtRQUM5QixJQUFJLEVBQUUsR0FBRyxLQUFLLDhIQUE4SDtRQUM1SSxZQUFZLEVBQUUsV0FBVyxTQUFTLENBQUMsT0FBTyxJQUFJLEVBQUUsRUFBRTtRQUNsRCxPQUFPLEVBQUUsTUFBTSxDQUFDLFNBQVMsQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDO1FBQ3hDLElBQUksRUFBRSxNQUFNLENBQUMsU0FBUyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7UUFDbEMsYUFBYSxFQUFFLHNDQUFzQyxLQUFLLEdBQUc7UUFDN0QsV0FBVyxFQUFFLFNBQVM7UUFDdEIsY0FBYyxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU87S0FDL0IsQ0FBQztBQUNKLENBQUM7QUFnQkQsaURBQWlEO0FBQ2pELEVBQUU7QUFDRiw0RUFBNEU7QUFDNUUsOEVBQThFO0FBQzlFLDRFQUE0RTtBQUM1RSw4REFBOEQ7QUFDOUQsRUFBRTtBQUNGLDhFQUE4RTtBQUM5RSx3RUFBd0U7QUFDeEUsOEVBQThFO0FBQzlFLDZDQUE2QztBQUM3QyxNQUFNLFVBQVUscUJBQXFCLENBQ25DLEtBQXdCO0lBRXhCLE9BQU87UUFDTCxNQUFNLEVBQUUsbUVBQW1FO1FBQzNFLEtBQUssRUFBRSxLQUFLLENBQUMsS0FBSztRQUNsQixTQUFTLEVBQ1Asb0tBQW9LO1FBQ3RLLE1BQU0sRUFBRSxLQUFLLENBQUMsS0FBSztZQUNqQixDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsS0FBSyxFQUFFO1lBQ3RDLENBQUMsQ0FBQyxLQUFLLENBQUMsTUFBTTtnQkFDWixDQUFDLENBQUM7b0JBQ0UsSUFBSSxFQUFFLE1BQU07b0JBQ1osSUFBSSxFQUFFLHFEQUFxRDtpQkFDNUQ7Z0JBQ0gsQ0FBQyxDQUFDLElBQUk7UUFDVixXQUFXLEVBQUUscUJBQXFCO0tBQ25DLENBQUM7QUFDSixDQUFDO0FBK0JELDZFQUE2RTtBQUM3RSx1RUFBdUU7QUFDdkUsTUFBTSxVQUFVLFlBQVksQ0FBQyxLQUFhLEVBQUUsS0FBbUI7SUFDN0QsTUFBTSxLQUFLLEdBQWlCO1FBQzFCLE9BQU8sRUFBRSxFQUFFO1FBQ1gsS0FBSyxFQUFFLEVBQUU7UUFDVCxNQUFNLEVBQUUsRUFBRTtRQUNWLElBQUksRUFBRSxFQUFFO1FBQ1IsV0FBVyxFQUFFLEVBQUU7UUFDZixXQUFXLEVBQUUsSUFBSTtRQUNqQixNQUFNLEVBQUUsRUFBRTtRQUNWLE1BQU0sRUFBRSxFQUFFO0tBQ1gsQ0FBQztJQUNGLElBQUksS0FBSyxLQUFLLFFBQVE7UUFDcEIsT0FBTztZQUNMLEdBQUcsS0FBSztZQUNSLE9BQU8sRUFBRSxxQkFBcUI7WUFDOUIsS0FBSyxFQUNILCtFQUErRTtZQUNqRixNQUFNLEVBQUU7Z0JBQ047b0JBQ0UsRUFBRSxFQUFFLGdCQUFnQjtvQkFDcEIsS0FBSyxFQUFFLGVBQWU7b0JBQ3RCLElBQUksRUFBRSxPQUFPO29CQUNiLFlBQVksRUFBRSxPQUFPO29CQUNyQixRQUFRLEVBQUUsSUFBSTtpQkFDZjthQUNGO1lBQ0QsV0FBVyxFQUFFLGlCQUFpQjtTQUMvQixDQUFDO0lBQ0osSUFBSSxLQUFLLEtBQUssT0FBTztRQUNuQixPQUFPO1lBQ0wsR0FBRyxLQUFLO1lBQ1IsT0FBTyxFQUFFLHVCQUF1QjtZQUNoQyxLQUFLLEVBQ0gsdUZBQXVGO1lBQ3pGLE1BQU0sRUFBRSxLQUFLLENBQUMsU0FBUztnQkFDckIsQ0FBQyxDQUFDO29CQUNFO3dCQUNFLEVBQUUsRUFBRSxjQUFjO3dCQUNsQixLQUFLLEVBQUUsY0FBYzt3QkFDckIsSUFBSSxFQUFFLFVBQVU7d0JBQ2hCLFlBQVksRUFBRSxjQUFjO3dCQUM1QixTQUFTLEVBQUUsRUFBRTt3QkFDYixRQUFRLEVBQUUsSUFBSTtxQkFDZjtvQkFDRDt3QkFDRSxFQUFFLEVBQUUsa0JBQWtCO3dCQUN0QixLQUFLLEVBQUUsa0JBQWtCO3dCQUN6QixJQUFJLEVBQUUsVUFBVTt3QkFDaEIsWUFBWSxFQUFFLGNBQWM7d0JBQzVCLFNBQVMsRUFBRSxFQUFFO3dCQUNiLFFBQVEsRUFBRSxJQUFJO3FCQUNmO2lCQUNGO2dCQUNILENBQUMsQ0FBQyxFQUFFO1lBQ04sSUFBSSxFQUFFLEtBQUssQ0FBQyxTQUFTO2dCQUNuQixDQUFDLENBQUMsb0RBQW9EO2dCQUN0RCxDQUFDLENBQUMsRUFBRTtZQUNOLFdBQVcsRUFBRSxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsRUFBRTtZQUN2RCxXQUFXLEVBQUUsS0FBSyxDQUFDLFNBQVM7Z0JBQzFCLENBQUMsQ0FBQyxJQUFJO2dCQUNOLENBQUMsQ0FBQztvQkFDRSxJQUFJLEVBQUUsZ0VBQWdFO29CQUN0RSxJQUFJLEVBQUUsVUFBVTtvQkFDaEIsS0FBSyxFQUFFLHNCQUFzQjtpQkFDOUI7U0FDTixDQUFDO0lBQ0osSUFBSSxLQUFLLEtBQUssUUFBUTtRQUNwQixPQUFPO1lBQ0wsR0FBRyxLQUFLO1lBQ1IsT0FBTyxFQUFFLEtBQUssQ0FBQyxxQkFBcUI7Z0JBQ2xDLENBQUMsQ0FBQyxnQkFBZ0I7Z0JBQ2xCLENBQUMsQ0FBQyxtQkFBbUI7WUFDdkIsS0FBSyxFQUFFLEtBQUssQ0FBQyxxQkFBcUI7Z0JBQ2hDLENBQUMsQ0FBQyx3REFBd0Q7Z0JBQzFELENBQUMsQ0FBQyxpREFBaUQ7WUFDckQsV0FBVyxFQUNULENBQUMsS0FBSyxDQUFDLHFCQUFxQixJQUFJLEtBQUssQ0FBQyxTQUFTO2dCQUM3QyxDQUFDLENBQUMsdUJBQXVCO2dCQUN6QixDQUFDLENBQUMsRUFBRTtZQUNSLFdBQVcsRUFDVCxDQUFDLEtBQUssQ0FBQyxxQkFBcUIsSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTO2dCQUM5QyxDQUFDLENBQUM7b0JBQ0UsSUFBSSxFQUFFLDhEQUE4RDtpQkFDckU7Z0JBQ0gsQ0FBQyxDQUFDLElBQUk7U0FDWCxDQUFDO0lBQ0osT0FBTztRQUNMLEdBQUcsS0FBSztRQUNSLE9BQU8sRUFBRSxpQkFBaUI7UUFDMUIsS0FBSyxFQUNILHlGQUF5RjtRQUMzRixNQUFNLEVBQUUsS0FBSyxDQUFDLGFBQWE7WUFDekIsQ0FBQyxDQUFDLGlDQUFpQztZQUNuQyxDQUFDLENBQUMsaUNBQWlDO1FBQ3JDLFdBQVcsRUFBRSxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLEVBQUU7UUFDdkUsTUFBTSxFQUFFO1lBQ047Z0JBQ0UsRUFBRSxFQUFFLG9CQUFvQjtnQkFDeEIsSUFBSSxFQUFFLFdBQVc7Z0JBQ2pCLEtBQUssRUFBRSw2QkFBNkI7YUFDckM7WUFDRCxHQUFHLENBQUMsS0FBSyxDQUFDLGFBQWE7Z0JBQ3JCLENBQUMsQ0FBQyxFQUFFO2dCQUNKLENBQUMsQ0FBQztvQkFDRTt3QkFDRSxFQUFFLEVBQUUscUJBQXFCO3dCQUN6QixJQUFJLEVBQUUsV0FBb0I7d0JBQzFCLEtBQUssRUFBRSx5QkFBeUI7cUJBQ2pDO2lCQUNGLENBQUM7U0FDUDtLQUNGLENBQUM7QUFDSixDQUFDO0FBV0QsK0VBQStFO0FBQy9FLDRFQUE0RTtBQUM1RSw2Q0FBNkM7QUFDN0MsTUFBTSxVQUFVLGdCQUFnQixDQUFDLEtBR2hDO0lBQ0MsT0FBTztRQUNMLEtBQUssRUFBRTtZQUNMLEVBQUUsRUFBRSxPQUFPO1lBQ1gsS0FBSyxFQUFFLE9BQU87WUFDZCxJQUFJLEVBQUUsT0FBTztZQUNiLFlBQVksRUFBRSxVQUFVO1lBQ3hCLEtBQUssRUFBRSxLQUFLLENBQUMsS0FBSztZQUNsQixXQUFXLEVBQUUsaUJBQWlCO1NBQy9CO1FBQ0QsUUFBUSxFQUFFO1lBQ1IsRUFBRSxFQUFFLFVBQVU7WUFDZCxLQUFLLEVBQUUsVUFBVTtZQUNqQixJQUFJLEVBQUUsVUFBVTtZQUNoQixZQUFZLEVBQUUsa0JBQWtCO1lBQ2hDLEtBQUssRUFBRSxLQUFLLENBQUMsUUFBUTtZQUNyQixXQUFXLEVBQUUsWUFBWTtTQUMxQjtRQUNELE1BQU0sRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRTtRQUM5QyxNQUFNLEVBQUUsRUFBRSxFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRTtRQUN0RCxNQUFNLEVBQUU7WUFDTixFQUFFLEVBQUUsbUJBQW1CO1lBQ3ZCLElBQUksRUFBRSxPQUFPO1lBQ2IsS0FBSyxFQUFFLGFBQWE7WUFDcEIsSUFBSSxFQUFFLFNBQVM7WUFDZixLQUFLLEVBQUUsVUFBVTtTQUNsQjtRQUNELE1BQU0sRUFBRTtZQUNOLEVBQUUsRUFBRSxjQUFjO1lBQ2xCLElBQUksRUFBRSxRQUFRO1lBQ2QsS0FBSyxFQUFFLE1BQU07WUFDYixJQUFJLEVBQUUsV0FBVztZQUNqQixLQUFLLEVBQUUsbUJBQW1CO1NBQzNCO0tBQ0YsQ0FBQztBQUNKLENBQUM7QUFFRCxnRkFBZ0Y7QUFDaEYsaUZBQWlGO0FBQ2pGLCtFQUErRTtBQUMvRSxnRkFBZ0Y7QUFDaEYsTUFBTSxVQUFVLGlCQUFpQixDQUFDLE1BQWM7SUFDOUMsT0FBTywrREFBK0QsTUFBTSxHQUFHLENBQUM7QUFDbEYsQ0FBQztBQUVELDhFQUE4RTtBQUM5RSw4QkFBOEI7QUFDOUIsOEVBQThFO0FBRTlFLDhFQUE4RTtBQUM5RSwyRUFBMkU7QUFDM0UsK0RBQStEO0FBQy9ELE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxPQUloQztJQUNDLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxVQUFVLElBQUksSUFBSSxDQUFDO0lBQzVDLElBQUksT0FBTyxHQUFHLEtBQUssQ0FBQztJQUNwQixJQUFJLEtBQW9DLENBQUM7SUFDekMsTUFBTSxJQUFJLEdBQUcsR0FBRyxFQUFFO1FBQ2hCLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFDZixZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDdEIsQ0FBQyxDQUFDO0lBQ0YsTUFBTSxHQUFHLEdBQUcsS0FBSyxJQUFJLEVBQUU7UUFDckIsSUFBSSxPQUFPO1lBQUUsT0FBTztRQUNwQixJQUFJLFFBQVEsR0FBRyxLQUFLLENBQUM7UUFDckIsSUFBSSxDQUFDO1lBQ0gsUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ25DLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCw0Q0FBNEM7UUFDOUMsQ0FBQztRQUNELElBQUksT0FBTztZQUFFLE9BQU87UUFDcEIsSUFBSSxRQUFRLEVBQUUsQ0FBQztZQUNiLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUNyQixPQUFPO1FBQ1QsQ0FBQztRQUNELEtBQUssR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ3BDLENBQUMsQ0FBQztJQUNGLEtBQUssR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ2xDLE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ofidj/entry",
3
- "version": "3.10.1",
3
+ "version": "3.11.0",
4
4
  "description": "The Fidj entry: sign-in, account recovery, the service agreement and the design system, shared verbatim by Fidj, the generated apps and the console.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -14,6 +14,14 @@
14
14
  "types": "./index.d.ts",
15
15
  "default": "./index.js"
16
16
  },
17
+ "./dom": {
18
+ "types": "./dom.d.ts",
19
+ "default": "./dom.js"
20
+ },
21
+ "./window": {
22
+ "types": "./window.d.ts",
23
+ "default": "./window.js"
24
+ },
17
25
  "./tokens.css": "./tokens.css",
18
26
  "./fonts.css": "./fonts.css",
19
27
  "./style.css": "./style.css"
@@ -0,0 +1,3 @@
1
+ export declare function signInHint(appId: string): string;
2
+ export declare function rememberSignIn(appId: string, label: string): void;
3
+ export declare function forgetSignIn(appId: string): void;
package/remembered.js ADDED
@@ -0,0 +1,33 @@
1
+ // What this browser remembers about the last person to sign in here.
2
+ //
3
+ // Not DOM and not a view layer, so it sits at the root beside the model: a
4
+ // React app needs this exactly as much as a hand-written one. It is the only
5
+ // browser API the root touches, every call is guarded, and a runtime without
6
+ // storage gets "nobody was here" rather than an exception — which is also the
7
+ // right answer in a private window.
8
+ const hintKey = (appId) => "fidj.entry." + appId;
9
+ export function signInHint(appId) {
10
+ try {
11
+ return localStorage.getItem(hintKey(appId)) || "";
12
+ }
13
+ catch {
14
+ return "";
15
+ }
16
+ }
17
+ // Remembered on this app's own origin, about this app's own member: no
18
+ // cross-site question is asked, and none is answered. Signing out forgets, so a
19
+ // shared browser does not show the next person an address.
20
+ export function rememberSignIn(appId, label) {
21
+ try {
22
+ if (label)
23
+ localStorage.setItem(hintKey(appId), label);
24
+ }
25
+ catch { }
26
+ }
27
+ export function forgetSignIn(appId) {
28
+ try {
29
+ localStorage.removeItem(hintKey(appId));
30
+ }
31
+ catch { }
32
+ }
33
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVtZW1iZXJlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9yZW1lbWJlcmVkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLHFFQUFxRTtBQUNyRSxFQUFFO0FBQ0YsMkVBQTJFO0FBQzNFLDZFQUE2RTtBQUM3RSw2RUFBNkU7QUFDN0UsOEVBQThFO0FBQzlFLG9DQUFvQztBQUVwQyxNQUFNLE9BQU8sR0FBRyxDQUFDLEtBQWEsRUFBRSxFQUFFLENBQUMsYUFBYSxHQUFHLEtBQUssQ0FBQztBQUV6RCxNQUFNLFVBQVUsVUFBVSxDQUFDLEtBQWE7SUFDdEMsSUFBSSxDQUFDO1FBQ0gsT0FBTyxZQUFZLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNwRCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxFQUFFLENBQUM7SUFDWixDQUFDO0FBQ0gsQ0FBQztBQUVELHVFQUF1RTtBQUN2RSxnRkFBZ0Y7QUFDaEYsMkRBQTJEO0FBQzNELE1BQU0sVUFBVSxjQUFjLENBQUMsS0FBYSxFQUFFLEtBQWE7SUFDekQsSUFBSSxDQUFDO1FBQ0gsSUFBSSxLQUFLO1lBQUUsWUFBWSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUFDLE1BQU0sQ0FBQyxDQUFBLENBQUM7QUFDWixDQUFDO0FBRUQsTUFBTSxVQUFVLFlBQVksQ0FBQyxLQUFhO0lBQ3hDLElBQUksQ0FBQztRQUNILFlBQVksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDMUMsQ0FBQztJQUFDLE1BQU0sQ0FBQyxDQUFBLENBQUM7QUFDWixDQUFDIn0=
package/window.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { openProviderWindow, relayProviderAnswer, type ProviderWindow, } from "./provider-window.js";
package/window.js ADDED
@@ -0,0 +1,7 @@
1
+ // The Fidj window, and the answer it relays back.
2
+ //
3
+ // A subpath of its own because it is neither pure nor a view layer: it drives
4
+ // `window.open` and `postMessage`, which every framework reaches for the same
5
+ // way. An app on React or Vue uses this exactly as a hand-written shell does.
6
+ export { openProviderWindow, relayProviderAnswer, } from "./provider-window.js";
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2luZG93LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3dpbmRvdy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxrREFBa0Q7QUFDbEQsRUFBRTtBQUNGLDhFQUE4RTtBQUM5RSw4RUFBOEU7QUFDOUUsOEVBQThFO0FBQzlFLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIsbUJBQW1CLEdBRXBCLE1BQU0sc0JBQXNCLENBQUMifQ==
package/screens.d.ts DELETED
@@ -1,19 +0,0 @@
1
- export declare const escape: (value: unknown) => string;
2
- export declare function masthead(logo: string, title: string): string;
3
- export declare function highlightCells(entries?: Array<{
4
- heading: string;
5
- body: string;
6
- }>): string;
7
- export declare function badgeStrip(entries?: string[]): string;
8
- export declare function credentialFields(state: {
9
- email: string;
10
- password: string;
11
- }): string;
12
- export type AccountState = {
13
- linkToken?: string;
14
- verificationConfirmed?: boolean;
15
- emailVerified?: boolean;
16
- accountEmail?: string;
17
- };
18
- export declare function accountForm(route: string, state: AccountState): string;
19
- export declare function returnNotice(asking: string): string;
package/screens.js DELETED
@@ -1,52 +0,0 @@
1
- // The screens the entry draws, as markup.
2
- //
3
- // Lifted out of generator-fidj's content.ts, where the only way to ship them
4
- // was to generate an app. What stayed behind is the shell that holds them:
5
- // the sign-in screen nests signin-trust inside its inner div and the
6
- // interaction screen makes it a sibling, so a single shell builder would have
7
- // had to change one of them. That is a decision, not a move.
8
- export const escape = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
9
- // The app's own mark beside its own name, decorative because the name is right
10
- // there: every shell opens with this.
11
- export function masthead(logo, title) {
12
- return `<header class="signin-masthead"><img class="app-mark" src="${escape(logo)}" alt=""><strong>${escape(title)}</strong></header>`;
13
- }
14
- export function highlightCells(entries) {
15
- if (!entries?.length)
16
- return "";
17
- return `<div class="signin-highlights">${entries
18
- .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>`)
19
- .join("")}</div>`;
20
- }
21
- export function badgeStrip(entries) {
22
- if (!entries?.length)
23
- return "";
24
- return `<footer class="signin-badges">${entries
25
- .map((entry) => `<span>${escape(entry)}</span>`)
26
- .join("")}</footer>`;
27
- }
28
- // Both doors, and what was already typed. The entry is rebuilt on every render
29
- // and a refused sign-in is a render, so the values come back in rather than
30
- // being lost with the markup that held them.
31
- export function credentialFields(state) {
32
- return `<label for="email">Email</label><input id="email" type="email" value="${escape(state.email)}" placeholder="you@company.com" autocomplete="username"><div class="field-head"><label for="password">Password</label><a href="#/forgot">Forgot?</a></div><div class="password-field"><input id="password" type="password" value="${escape(state.password)}" placeholder="••••••••••" autocomplete="current-password"><button type="button" id="reveal" aria-controls="password">Show</button></div><button class="primary" type="submit" name="entry" value="credentials">Continue</button><button class="secondary" type="submit" name="signup" value="true">Create an account</button>`;
33
- }
34
- // The four account screens. My account is shown inside the app; the recovery
35
- // and verification ones are reached without a session and stand alone.
36
- export function accountForm(route, state) {
37
- return route === "forgot"
38
- ? `<h2>Reset your password</h2><p>We’ll email you a link to choose a new password for your shared Fidj account.</p><form id="recovery"><label for="recovery-email">Email address</label><input id="recovery-email" type="email" autocomplete="email" required><button class="primary">Send reset link</button></form>`
39
- : route === "reset"
40
- ? `<h2>Choose a new password</h2><p>This changes your Fidj password across all your apps and signs out existing sessions.</p>${state.linkToken ? '<form id="recovery"><label for="new-password">New password</label><input id="new-password" type="password" autocomplete="new-password" minlength="12" required><label for="confirm-password">Confirm password</label><input id="confirm-password" type="password" autocomplete="new-password" minlength="12" required><p>Use at least 12 characters (up to 72 UTF-8 bytes).</p><button class="primary">Save new password</button></form>' : '<p>Request a new link if you no longer have an active reset link.</p><a href="#/forgot">Request a reset link</a>'}`
41
- : route === "verify"
42
- ? `<h2>${state.verificationConfirmed ? "Email verified" : "Verify your email"}</h2>${state.verificationConfirmed ? "<p>Your account is ready. Return to your app to continue.</p>" : "<p>Confirm that this email address belongs to you.</p>"}${state.verificationConfirmed ? "" : state.linkToken ? '<form id="recovery"><button class="primary">Confirm email address</button></form>' : "<p>Sign in to your account to request a new verification email.</p>"}`
43
- : `<h2>My Fidj account</h2><p class="account-identity">Signed in as <strong>${escape(state.accountEmail)}</strong></p><p>Your identity is shared across your apps. Privacy choices remain separate for each app.</p><p id="verification-status">${state.emailVerified ? "Your email address is verified." : "Your email is not verified yet."}</p><button id="check-verification">Refresh verification status</button>${state.emailVerified ? "" : '<button id="resend-verification">Send verification email</button>'}<p><a href="#/forgot">Reset my password</a></p>`;
44
- }
45
- // A window that opened on its own, over the page somebody was on, owes them the
46
- // way out before it asks for anything. Naming the app they came from is also the
47
- // only thing on this screen that they can check against what they were doing a
48
- // second ago — which is exactly what a page asking for a password should offer.
49
- export function returnNotice(asking) {
50
- return `<p class="signin-return" role="note">When you are done, this window closes and takes you back to ${escape(asking)}.</p>`;
51
- }
52
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NyZWVucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9zY3JlZW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLDBDQUEwQztBQUMxQyxFQUFFO0FBQ0YsNkVBQTZFO0FBQzdFLDJFQUEyRTtBQUMzRSxxRUFBcUU7QUFDckUsOEVBQThFO0FBQzlFLDZEQUE2RDtBQUU3RCxNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUcsQ0FBQyxLQUFjLEVBQUUsRUFBRSxDQUN2QyxNQUFNLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FDekIsVUFBVSxFQUNWLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FDUCxDQUFDLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FDdkUsSUFBSSxDQUNKLENBQ0wsQ0FBQztBQUVKLCtFQUErRTtBQUMvRSxzQ0FBc0M7QUFDdEMsTUFBTSxVQUFVLFFBQVEsQ0FBQyxJQUFZLEVBQUUsS0FBYTtJQUNsRCxPQUFPLDhEQUE4RCxNQUFNLENBQUMsSUFBSSxDQUFDLG9CQUFvQixNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixDQUFDO0FBQ3pJLENBQUM7QUFFRCxNQUFNLFVBQVUsY0FBYyxDQUM1QixPQUFrRDtJQUVsRCxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU07UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUNoQyxPQUFPLGtDQUFrQyxPQUFPO1NBQzdDLEdBQUcsQ0FDRixDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUNmLCtCQUErQixNQUFNLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLFdBQVcsTUFBTSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsV0FBVyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FDako7U0FDQSxJQUFJLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQztBQUN0QixDQUFDO0FBRUQsTUFBTSxVQUFVLFVBQVUsQ0FBQyxPQUFrQjtJQUMzQyxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU07UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUNoQyxPQUFPLGlDQUFpQyxPQUFPO1NBQzVDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsU0FBUyxNQUFNLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQztTQUMvQyxJQUFJLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQztBQUN6QixDQUFDO0FBRUQsK0VBQStFO0FBQy9FLDRFQUE0RTtBQUM1RSw2Q0FBNkM7QUFDN0MsTUFBTSxVQUFVLGdCQUFnQixDQUFDLEtBQTBDO0lBQ3pFLE9BQU8seUVBQXlFLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLHFPQUFxTyxNQUFNLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxnVUFBZ1UsQ0FBQztBQUNqcUIsQ0FBQztBQVNELDZFQUE2RTtBQUM3RSx1RUFBdUU7QUFDdkUsTUFBTSxVQUFVLFdBQVcsQ0FBQyxLQUFhLEVBQUUsS0FBbUI7SUFDNUQsT0FBVSxLQUFLLEtBQUssUUFBUTtRQUN4QixDQUFDLENBQUMsb1RBQW9UO1FBQ3RULENBQUMsQ0FBQyxLQUFLLEtBQUssT0FBTztZQUNqQixDQUFDLENBQUMsNkhBQTZILEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLDBhQUEwYSxDQUFDLENBQUMsQ0FBQyxrSEFBa0gsRUFBRTtZQUNsckIsQ0FBQyxDQUFDLEtBQUssS0FBSyxRQUFRO2dCQUNsQixDQUFDLENBQUMsT0FBTyxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxtQkFBbUIsUUFBUSxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQyxDQUFDLCtEQUErRCxDQUFDLENBQUMsQ0FBQyx3REFBd0QsR0FBRyxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsbUZBQW1GLENBQUMsQ0FBQyxDQUFDLHFFQUFxRSxFQUFFO2dCQUNsYyxDQUFDLENBQUMsNEVBQTRFLE1BQU0sQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLDBJQUEwSSxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxpQ0FBaUMsQ0FBQyxDQUFDLENBQUMsaUNBQWlDLDJFQUEyRSxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLG1FQUFtRSxpREFBaUQsQ0FBQztBQUNsakIsQ0FBQztBQUVELGdGQUFnRjtBQUNoRixpRkFBaUY7QUFDakYsK0VBQStFO0FBQy9FLGdGQUFnRjtBQUNoRixNQUFNLFVBQVUsWUFBWSxDQUFDLE1BQWM7SUFDekMsT0FBTyxvR0FBb0csTUFBTSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUM7QUFDbkksQ0FBQyJ9
@@ -1,13 +0,0 @@
1
- export declare function agreementMarkup(): string;
2
- export declare function acceptedAgreement(form: HTMLFormElement): {
3
- termsAccepted: boolean;
4
- termsVersion: string;
5
- } | null;
6
- 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.";
7
- export declare function bindAgreement(form: HTMLFormElement | null, title: string, endpoint: string, appId: string, checked?: boolean): Promise<void>;
8
- export declare function signInHint(appId: string): string;
9
- export declare function rememberSignIn(appId: string, label: string): void;
10
- export declare function forgetSignIn(appId: string): void;
11
- export type SigninShape = "button" | "inline" | "both";
12
- export declare function providerEntry(title: string, appId: string, credentials: string, isFidjItself?: boolean, shape?: SigninShape): string;
13
- export declare function showEmailEntry(open: boolean, focus?: boolean): void;