@franzzemen/brokenstock-shell 0.4.4 → 0.4.5

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.
@@ -233,8 +233,17 @@ path here is bounded regardless.
233
233
  fall back to running uncoordinated — today's behaviour — never throw. A missing
234
234
  API must not be the reason nobody can sign in.
235
235
  */
236
- const LOCKS_AVAILABLE = typeof navigator !== 'undefined'
237
- && typeof navigator.locks?.request === 'function';
236
+ /**
237
+ * Checked at CALL time, not once at module load.
238
+ *
239
+ * Module-level would be evaluated during import — before `navigator` exists at
240
+ * all under prerendering, and before a test can arrange anything — permanently
241
+ * fixing the answer to "no" for the life of the process. Found by writing the
242
+ * test for the fallback path, which could not be reached any other way.
243
+ */
244
+ function locksAvailable() {
245
+ return typeof navigator !== 'undefined' && typeof navigator.locks?.request === 'function';
246
+ }
238
247
  /**
239
248
  * Runs `work` if this window can take the lock right now.
240
249
  *
@@ -246,7 +255,7 @@ const LOCKS_AVAILABLE = typeof navigator !== 'undefined'
246
255
  * deliberate degradation to today's behaviour rather than a refusal.
247
256
  */
248
257
  async function runExclusive(name, work) {
249
- if (!LOCKS_AVAILABLE) {
258
+ if (!locksAvailable()) {
250
259
  console.warn(`[shell] navigator.locks unavailable; "${name}" runs without cross-window coordination`);
251
260
  return { ran: true, value: await work() };
252
261
  }
@@ -281,6 +290,40 @@ async function awaitChange(read, unchanged, timeoutMs, pollMs = 100) {
281
290
  }
282
291
  }
283
292
 
293
+ /*
294
+ Created by Franz Zemen 2026-08-29
295
+ License Type: UNLICENSED
296
+
297
+ What a window should do once it can act on a session renewal
298
+ (multiple-windows PRD D5).
299
+
300
+ WHY THIS IS A NAMED FUNCTION RATHER THAN THREE `if`s INSIDE THE SERVICE. The
301
+ rule was originally written as "retry with a token different from the one you
302
+ failed with", which is not a well-defined instruction: the in-window
303
+ single-flight already collapses requests that were sent with DIFFERENT tokens,
304
+ so "the one you failed with" is not a single value. Pulling the decision out
305
+ forces the three cases to be named, and lets them be tested without mocking a
306
+ network call — which is what they deserve, because getting this wrong shows up
307
+ hours later as "a window signed me out" with nothing linking cause to effect.
308
+ */
309
+ /**
310
+ * @param before the token this window held immediately BEFORE asking for the lock
311
+ * @param current the token in shared storage now that this window can act
312
+ */
313
+ function decideRenewal(before, current) {
314
+ // Gone. Retrying would present nothing and 401 again; worse, it would look
315
+ // like a transient failure when it is a definite answer.
316
+ if (before && !current)
317
+ return 'sign-out';
318
+ // Someone else's fresh token. Rotating again would invalidate the token every
319
+ // OTHER window just adopted — turning one window's recovery into everyone
320
+ // else's sign-out.
321
+ if (current && current !== before)
322
+ return 'adopt';
323
+ // Unchanged, or nothing stored to begin with.
324
+ return 'renew';
325
+ }
326
+
284
327
  /** Name of the cross-window renewal lock (PRD D5). */
285
328
  const RENEWAL_LOCK = 'brokenstock.session.renew';
286
329
  /** How long a window waits for the renewing window to publish a token (PRD D5). */
@@ -547,17 +590,15 @@ class AuthenticationService {
547
590
  */
548
591
  async #renewOrAdopt(before) {
549
592
  const current = this.#store.read();
550
- // Gone: whoever held the lock before us renewed and then failed permanently,
551
- // or signed out. Retrying would present nothing and 401 again.
552
- if (before && !current)
553
- return false;
554
- // Someone renewed while we queued. Adopt their work; do not rotate again —
555
- // a second rotation would invalidate the token every other window just took.
556
- if (current && current !== before) {
557
- this.#auth = current;
558
- return true;
593
+ switch (decideRenewal(before, current)) {
594
+ case 'sign-out':
595
+ return false;
596
+ case 'adopt':
597
+ this.#auth = current;
598
+ return true;
599
+ case 'renew':
600
+ return this.#renew();
559
601
  }
560
- return this.#renew();
561
602
  }
562
603
  /**
563
604
  * The renewal request itself.
@@ -3165,5 +3206,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
3165
3206
  * Generated bundle index. Do not edit.
3166
3207
  */
3167
3208
 
3168
- export { AuthenticationService, CLEAR_USAGE, CommandRegistryService, CommandService, CommandTerminal, ConfirmDialog, FAQ_CONTENT_LOADER, FAQ_DEV_EDITOR, FAQ_DEV_MODE, FAQ_DEV_WORKSPACE_ROOT, FAQ_PUBLIC_PUBLISHER, FAQ_PUBLIC_SITE_BASE_URL, FaqAuthoringPanel, FaqHistoryService, FaqMarkdownRendererService, FaqModal, FaqService, HELP_USAGE, HttpFaqContentLoader, KeyboardShortcutService, Login, MainContent, PublicFaqIndex, PublicFaqPage, PublicFaqToc, RegexValidatorDirective, SESSION_TOKEN_KEY, SHELL_APP_NAME, SHELL_AUTH_URLS, SHELL_COMMANDS, SHELL_MENU_ITEMS, SHELL_POST_LOGIN_ROUTE, SHELL_SHARED_SESSION, SessionTokenStore, Shell, SideMenu, StatusBar, TerminalFocusService, TerminalVisibilityService, ThemeService, ThinkingService, audienceAllowed, authGuard, authInterceptor, buildFallbackChain, guestGuard, provideShellConfig, tokenize };
3209
+ export { AuthenticationService, CLEAR_USAGE, CommandRegistryService, CommandService, CommandTerminal, ConfirmDialog, FAQ_CONTENT_LOADER, FAQ_DEV_EDITOR, FAQ_DEV_MODE, FAQ_DEV_WORKSPACE_ROOT, FAQ_PUBLIC_PUBLISHER, FAQ_PUBLIC_SITE_BASE_URL, FaqAuthoringPanel, FaqHistoryService, FaqMarkdownRendererService, FaqModal, FaqService, HELP_USAGE, HttpFaqContentLoader, KeyboardShortcutService, Login, MainContent, PublicFaqIndex, PublicFaqPage, PublicFaqToc, RegexValidatorDirective, SESSION_TOKEN_KEY, SHELL_APP_NAME, SHELL_AUTH_URLS, SHELL_COMMANDS, SHELL_MENU_ITEMS, SHELL_POST_LOGIN_ROUTE, SHELL_SHARED_SESSION, SessionTokenStore, Shell, SideMenu, StatusBar, TerminalFocusService, TerminalVisibilityService, ThemeService, ThinkingService, audienceAllowed, authGuard, authInterceptor, buildFallbackChain, decideRenewal, guestGuard, provideShellConfig, tokenize };
3169
3210
  //# sourceMappingURL=franzzemen-brokenstock-shell.mjs.map