@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/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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsc0JBQXNCO0FBQ3RCLEVBQUU7QUFDRiwrRUFBK0U7QUFDL0UsMkVBQTJFO0FBQzNFLHNFQUFzRTtBQUN0RSxnRkFBZ0Y7QUFDaEYsc0VBQXNFO0FBQ3RFLEVBQUU7QUFDRixnRkFBZ0Y7QUFDaEYsK0VBQStFO0FBQy9FLGdGQUFnRjtBQUNoRiw4RUFBOEU7QUFDOUUsMkJBQTJCO0FBSTNCLDhFQUE4RTtBQUM5RSx1QkFBdUI7QUFDdkIsOEVBQThFO0FBRTlFLE1BQU0sa0JBQWtCLEdBQUcsb0JBQW9CLENBQUM7QUFFaEQscUVBQXFFO0FBQ3JFLEVBQUU7QUFDRixnRkFBZ0Y7QUFDaEYsNEVBQTRFO0FBQzVFLDBFQUEwRTtBQUMxRSw4RUFBOEU7QUFDOUUsOEVBQThFO0FBQzlFLE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxLQUFjO0lBQzlDLElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3RELE1BQU0sTUFBTSxHQUFHLEtBSWQsQ0FBQztJQUNGLElBQUksTUFBTSxDQUFDLElBQUksS0FBSyxHQUFHO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDckMsS0FBSyxNQUFNLEtBQUssSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNqRSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDO1lBQ2pFLE9BQU8sSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFFRCxnRkFBZ0Y7QUFDaEYsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSwyRUFBMkU7QUFDM0UsZ0ZBQWdG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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ofidj/entry",
|
|
3
|
-
"version": "3.
|
|
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"
|
package/remembered.d.ts
ADDED
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/service-agreement.d.ts
DELETED
|
@@ -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;
|