@ofidj/generator-fidj 3.10.0 → 3.10.2

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.
@@ -308,6 +308,60 @@ function offerTheWindowBack(providerWindow: ProviderWindow) {
308
308
  waiting.insertAdjacentElement("afterend", cancel);
309
309
  }
310
310
 
311
+ // Being recognised by Fidj is not the same as having signed in here. An app's
312
+ // door writes the session the provider holds, and Fidj's own entry used to read
313
+ // none of it — so somebody who had just joined an app was asked for a password
314
+ // by the account provider itself, one screen later.
315
+ //
316
+ // `prompt=none` asks the provider to answer without showing anything: a code
317
+ // when it knows this browser, `login_required` when it does not. Those are
318
+ // exactly the two cases to tell apart, and neither costs a screen.
319
+ //
320
+ // Only where Fidj is the app. An app with a door of its own must keep it: taking
321
+ // that door is where the app's own agreement is asked and recorded, and signing
322
+ // somebody in around it would skip a decision they never made.
323
+ const RECOGNITION_ASKED = "fidj.oidc.recognition-asked";
324
+ // A page that is public is exactly where a session is not what somebody came
325
+ // for. Asking there sent an anonymous visitor to the provider and back, and what
326
+ // they had asked to read never rendered.
327
+ const PUBLIC_ROUTE = "pub";
328
+ // Whether the question is still outstanding. Drawing the credential form and
329
+ // then navigating to the provider loses whatever somebody had begun typing, so
330
+ // the entry waits instead — the answer takes one redirect, not a wait worth a
331
+ // spinner's worth of ceremony, and it says what it is doing.
332
+ let recognising = false;
333
+ function mightBeRecognised() {
334
+ if (!oidc || !isFidjItself) return false;
335
+ if ((moduleRoute() || "").split("/")[0] === PUBLIC_ROUTE) return false;
336
+ if (oidc.signedOutHere()) return false;
337
+ try {
338
+ return sessionStorage.getItem(RECOGNITION_ASKED) !== "true";
339
+ } catch {
340
+ return false;
341
+ }
342
+ }
343
+
344
+ async function askWhetherFidjKnowsThisBrowser() {
345
+ if (!oidc || !isFidjItself || sdk.isLoggedIn() || oidc.signedOutHere())
346
+ return false;
347
+ if ((moduleRoute() || "").split("/")[0] === PUBLIC_ROUTE) return false;
348
+ // Once per document: the answer comes back as a redirect to this same page,
349
+ // so without this a refusal would ask again, and again.
350
+ try {
351
+ if (sessionStorage.getItem(RECOGNITION_ASKED) === "true") return false;
352
+ sessionStorage.setItem(RECOGNITION_ASKED, "true");
353
+ } catch {
354
+ return false;
355
+ }
356
+ try {
357
+ window.location.assign(await oidc.beginLogin({ silent: true }));
358
+ return true;
359
+ } catch {
360
+ // No provider, or it would not say. The entry is still there to be used.
361
+ return false;
362
+ }
363
+ }
364
+
311
365
  function signInThroughProvider(
312
366
  trigger: HTMLElement | null,
313
367
  options: { silent?: boolean; prompt?: string } = {},
@@ -544,6 +598,10 @@ function render() {
544
598
  renderNav("content");
545
599
  return;
546
600
  }
601
+ if (recognising) {
602
+ root.innerHTML = `<section class="signin-shell"><div class="signin-form"><p role="status">Checking whether you are already signed in to Fidj…</p></div></section>`;
603
+ return;
604
+ }
547
605
  // Only the entry reaches here now: the privacy screen and the account
548
606
  // screen became one, and that one is drawn above.
549
607
  root.innerHTML = `<section class="signin-shell"><div class="signin-intro${config.highlights?.length ? "" : " is-plain"}"><header class="signin-masthead"><img class="app-mark" src="${escape(config.logo)}" alt=""><strong>${escape(config.title)}</strong></header>
@@ -1081,6 +1139,7 @@ if (relayProviderAnswer()) {
1081
1139
 
1082
1140
  function boot() {
1083
1141
  window.addEventListener("hashchange", render);
1142
+ recognising = mightBeRecognised();
1084
1143
  render();
1085
1144
  if (readInteraction()) {
1086
1145
  render();
@@ -1100,7 +1159,27 @@ function boot() {
1100
1159
  if (oidc && new URL(window.location.href).searchParams.has("state")) {
1101
1160
  const callback = new URL(window.location.href);
1102
1161
  window.history.replaceState(null, "", window.location.pathname + "#/content");
1103
- await oidc.completeLogin(callback);
1162
+ try {
1163
+ await oidc.completeLogin(callback);
1164
+ } catch (refusal) {
1165
+ const why = (refusal as {code?: string; silentRefusal?: boolean}) || {};
1166
+ // A silent question answered "no" is an answer, not a failure. But the
1167
+ // two ways of saying no mean opposite things here.
1168
+ //
1169
+ // `login_required` — the provider does not know this browser, and the
1170
+ // entry is the right next screen.
1171
+ //
1172
+ // `consent_required` — it knows exactly who this is; what it has not got
1173
+ // is this client's permission, because the account was made through
1174
+ // another app and Fidj's console is a client of its own. Asking again
1175
+ // without `prompt=none` gets the screen that collects it, and no
1176
+ // password: the person already typed theirs once.
1177
+ if (why.code === "consent_required") {
1178
+ window.location.assign(await oidc.beginLogin());
1179
+ return;
1180
+ }
1181
+ if (!why.silentRefusal) throw refusal;
1182
+ }
1104
1183
  try {
1105
1184
  sessionStorage.removeItem("fidj.interaction.email");
1106
1185
  } catch {}
@@ -1109,10 +1188,16 @@ function boot() {
1109
1188
  apiEndpoint: config.apiEndpoint,
1110
1189
  prod: !config.localDemo,
1111
1190
  });
1191
+ recognising = recognising && !sdk.isLoggedIn();
1112
1192
  if (sdk.isLoggedIn()) {
1113
1193
  await refresh();
1114
1194
  if (!moduleRoute() && !accountRoutes.includes(currentRoute()))
1115
1195
  navigate("content");
1196
+ return;
1116
1197
  }
1198
+ // Nothing held here. Fidj may still know this browser from an app.
1199
+ if (await askWhetherFidjKnowsThisBrowser()) return;
1200
+ recognising = false;
1201
+ render();
1117
1202
  });
1118
1203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ofidj/generator-fidj",
3
- "version": "3.10.0",
3
+ "version": "3.10.2",
4
4
  "description": "Generate a TypeScript app with Fidj sign-in, live server-side roles and per-app privacy.",
5
5
  "homepage": "https://fidj.ovh",
6
6
  "bugs": {