@tidecloak/js 0.13.33-staging → 0.13.34-staging

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.
@@ -70,12 +70,20 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
70
70
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
71
71
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
72
72
  };
73
- var _TideCloak_instances, _TideCloak_refreshQueue, _TideCloak_adapter, _TideCloak_useNonce, _TideCloak_callbackStorage, _TideCloak_logInfo, _TideCloak_logWarn, _TideCloak_loginIframe, _TideCloak_dpopProvider, _TideCloak_config, _TideCloak_loadAdapter, _TideCloak_loadDefaultAdapter, _TideCloak_loadCordovaAdapter, _TideCloak_loadCordovaNativeAdapter, _TideCloak_loadConfig, _TideCloak_setupEndpoints, _TideCloak_loadOidcConfig, _TideCloak_setupOidcEndpoints, _TideCloak_getIssuerUrl, _TideCloak_check3pCookiesSupported, _TideCloak_processInit, _TideCloak_setupCheckLoginIframe, _TideCloak_checkLoginIframe, _TideCloak_checkSsoSilently, _TideCloak_parseCallback, _TideCloak_parseCallbackUrl, _TideCloak_parseCallbackParams, _TideCloak_processCallback, _TideCloak_scheduleCheckIframe, _TideCloak_fetchAccessToken, _TideCloak_fetchRefreshToken, _TideCloak_ensureRequestEnclaveOpen, _TideCloak_getVoucherUrl, _TideCloak_setToken, _TideCloak_getRealmUrl, _TideCloak_createLogger, _LocalStorage_instances, _LocalStorage_clearInvalidValues, _LocalStorage_clearAllValues, _LocalStorage_getStoredEntries, _LocalStorage_parseExpiry, _CookieStorage_instances, _CookieStorage_getCookie, _CookieStorage_setCookie, _CookieStorage_cookieExpiration;
73
+ var _TideCloak_instances, _TideCloak_refreshQueue, _TideCloak_adapter, _TideCloak_useNonce, _TideCloak_callbackStorage, _TideCloak_logInfo, _TideCloak_logWarn, _TideCloak_loginIframe, _TideCloak_dpopProvider, _TideCloak_config, _TideCloak_loadAdapter, _TideCloak_loadDefaultAdapter, _TideCloak_loadCordovaAdapter, _TideCloak_loadCordovaNativeAdapter, _TideCloak_loadConfig, _TideCloak_setupEndpoints, _TideCloak_loadOidcConfig, _TideCloak_setupOidcEndpoints, _TideCloak_getIssuerUrl, _TideCloak_check3pCookiesSupported, _TideCloak_processInit, _TideCloak_setupCheckLoginIframe, _TideCloak_checkLoginIframe, _TideCloak_checkSsoSilently, _TideCloak_parseCallback, _TideCloak_parseCallbackUrl, _TideCloak_parseCallbackParams, _TideCloak_processCallback, _TideCloak_scheduleCheckIframe, _TideCloak_consumePostLogoutMarker, _TideCloak_fetchAccessToken, _TideCloak_fetchRefreshToken, _TideCloak_ensureRequestEnclaveOpen, _TideCloak_reloginInteractively, _TideCloak_provideDokenOrRelogin, _TideCloak_getVoucherUrl, _TideCloak_setToken, _TideCloak_teardownEnclaves, _TideCloak_getRealmUrl, _TideCloak_createLogger, _LocalStorage_instances, _LocalStorage_clearInvalidValues, _LocalStorage_clearAllValues, _LocalStorage_getStoredEntries, _LocalStorage_parseExpiry, _CookieStorage_instances, _CookieStorage_getCookie, _CookieStorage_setCookie, _CookieStorage_cookieExpiration;
74
74
  Object.defineProperty(exports, "__esModule", { value: true });
75
75
  exports.NetworkError = exports.Models = exports.Tools = exports.PolicySignRequest = exports.ApprovalEnclaveNew = exports.ApprovalEnclave = exports.RequestEnclave = void 0;
76
76
  // MODIFIED: Added dependency to external Tide helper libraries.
77
77
  const heimdall_tide_1 = require("heimdall-tide");
78
78
  const CONTENT_TYPE_JSON = 'application/json';
79
+ // Post-logout marker. After an explicit logout there is no session to silently
80
+ // resume, so the immediately-following init() must skip the racy silent
81
+ // check-sso (whose hidden iframe can net::ERR_ABORTED and wedge the app) and go
82
+ // straight to interactive login. `logout()` stamps this durable localStorage
83
+ // marker (survives the logout redirect round-trip); init() consumes it exactly
84
+ // once and forces an interactive login when present + fresh.
85
+ const POST_LOGOUT_MARKER_KEY = 'tide-post-logout';
86
+ const POST_LOGOUT_MARKER_TTL_MS = 60000;
79
87
  /**
80
88
  * @typedef {Object} Endpoints
81
89
  * @property {() => string} authorize
@@ -149,6 +157,15 @@ class TideCloak {
149
157
  this.timeSkew = null;
150
158
  /** @type {boolean} */
151
159
  this.silentCheckSsoFallback = true;
160
+ /**
161
+ * Max time (ms) to wait for the hidden silent-check-sso iframe to post back
162
+ * before treating the attempt as "not authenticated". Guards against the
163
+ * post-logout wedge where the check-sso (or the Tide enclave it spins up)
164
+ * never responds and init() would otherwise hang forever. Overridable via
165
+ * initOptions.silentCheckSsoTimeout.
166
+ * @type {number}
167
+ */
168
+ this.silentCheckSsoTimeout = 10000;
152
169
  /** @type {TideCloakPkceMethod} */
153
170
  this.pkceMethod = 'S256';
154
171
  this.enableLogging = false;
@@ -227,6 +244,9 @@ class TideCloak {
227
244
  if (typeof initOptions.silentCheckSsoFallback === 'boolean') {
228
245
  this.silentCheckSsoFallback = initOptions.silentCheckSsoFallback;
229
246
  }
247
+ if (typeof initOptions.silentCheckSsoTimeout === 'number' && initOptions.silentCheckSsoTimeout > 0) {
248
+ this.silentCheckSsoTimeout = initOptions.silentCheckSsoTimeout;
249
+ }
230
250
  if (typeof initOptions.pkceMethod !== 'undefined') {
231
251
  if (initOptions.pkceMethod !== 'S256' && initOptions.pkceMethod !== false) {
232
252
  throw new TypeError(`Invalid value for pkceMethod', expected 'S256' or false but got ${initOptions.pkceMethod}.`);
@@ -273,9 +293,29 @@ class TideCloak {
273
293
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_check3pCookiesSupported).call(this);
274
294
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processInit).call(this, initOptions);
275
295
  (_c = this.onReady) === null || _c === void 0 ? void 0 : _c.call(this, this.authenticated);
276
- // initialize request enclave if authenticated
277
- if (this.doken && initOptions.setupRequestEnclave) {
278
- this.initRequestEnclave();
296
+ // Do NOT eagerly open the Tide RequestEnclave during init().
297
+ //
298
+ // On a post-logout silent re-auth, the doken returned by the silent
299
+ // check-sso can be STALE: its session key no longer matches the session the
300
+ // hidden enclave cached from the previous login. Opening the hidden enclave
301
+ // here makes it sit "waiting for doken refresh" forever - a refresh that
302
+ // never comes because no fresh doken exists - which is exactly the
303
+ // "[ENCLAVE] Received init but waiting for doken refresh" ... hang that
304
+ // wedges the SPA on "Signing you in..." on the second login. (The enclave
305
+ // silently WAITS rather than emitting an error, so heimdall's own
306
+ // requireReloginCallback recovery never fires.)
307
+ //
308
+ // Instead we DEFER enclave setup: it is created lazily on the first real
309
+ // Tide operation (encrypt / decrypt / approve / signDpopApproval - each
310
+ // calls initRequestEnclave()), by which point a fresh interactive login has
311
+ // minted a current doken and the enclave can complete its handshake. We
312
+ // still register the user-gesture listener so the popup fallback can open
313
+ // once an enclave has actually been created; #ensureRequestEnclaveOpen
314
+ // no-ops while there is no enclave or no doken.
315
+ if (initOptions.setupRequestEnclave) {
316
+ if (this.doken) {
317
+ __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDECLOAK] Deferring Tide RequestEnclave setup until first use (not opening during init to avoid a stale-doken handshake stall on silent re-auth).');
318
+ }
279
319
  // to get around popups requiring user gestures
280
320
  document.addEventListener('click', () => {
281
321
  __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_ensureRequestEnclaveOpen).call(this);
@@ -379,6 +419,16 @@ class TideCloak {
379
419
  this.logout = async (options) => {
380
420
  var _a;
381
421
  await ((_a = __classPrivateFieldGet(this, _TideCloak_dpopProvider, "f")) === null || _a === void 0 ? void 0 : _a.flush());
422
+ // Stamp the post-logout marker so the next init() forces interactive login
423
+ // instead of running the racy silent check-sso. See POST_LOGOUT_MARKER_KEY.
424
+ try {
425
+ if (typeof window !== 'undefined' && window.localStorage) {
426
+ window.localStorage.setItem(POST_LOGOUT_MARKER_KEY, Date.now().toString());
427
+ }
428
+ }
429
+ catch (e) {
430
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] could not set marker: ' + (e instanceof Error ? e.message : String(e)));
431
+ }
382
432
  return __classPrivateFieldGet(this, _TideCloak_adapter, "f").logout(options);
383
433
  };
384
434
  /**
@@ -667,19 +717,8 @@ class TideCloak {
667
717
  isRunningLocal: new URL(__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getVoucherUrl).call(this)).hostname === "localhost"
668
718
  }).init({
669
719
  doken: this.doken,
670
- dokenRefreshCallback: async () => {
671
- await this.ensureTokenReady();
672
- if (!this.doken)
673
- throw new Error('[TIDECLOAK] No doken found');
674
- return this.doken;
675
- },
676
- requireReloginCallback: async () => {
677
- await this.login({
678
- idpHint: 'tide',
679
- prompt: 'login',
680
- redirectUri: window.location.href
681
- });
682
- }
720
+ dokenRefreshCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_provideDokenOrRelogin).call(this),
721
+ requireReloginCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this)
683
722
  });
684
723
  }
685
724
  }
@@ -701,19 +740,8 @@ class TideCloak {
701
740
  backgroundUrl: __classPrivateFieldGet(this, _TideCloak_config, "f")['backgroundUrl'],
702
741
  logoUrl: __classPrivateFieldGet(this, _TideCloak_config, "f")['logoUrl'],
703
742
  doken: this.doken,
704
- dokenRefreshCallback: async () => {
705
- await this.ensureTokenReady();
706
- if (!this.doken)
707
- throw new Error('[TIDECLOAK] No doken found');
708
- return this.doken;
709
- },
710
- requireReloginCallback: async () => {
711
- await this.login({
712
- idpHint: 'tide',
713
- prompt: 'login',
714
- redirectUri: window.location.href
715
- });
716
- }
743
+ dokenRefreshCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_provideDokenOrRelogin).call(this),
744
+ requireReloginCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this)
717
745
  });
718
746
  }
719
747
  }
@@ -1444,6 +1472,15 @@ async function _TideCloak_processInit(initOptions) {
1444
1472
  await this.login(options);
1445
1473
  };
1446
1474
  const onLoad = async () => {
1475
+ // Post-logout deterministic path: if the previous logout stamped a fresh
1476
+ // marker, skip silent check-sso entirely and go straight to interactive
1477
+ // login. There is no session to silently resume after an explicit logout,
1478
+ // and the silent path is exactly where the ERR_ABORTED wedge lives.
1479
+ if (__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_consumePostLogoutMarker).call(this)) {
1480
+ __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDE-POSTLOGOUT] marker honored in init(); forcing interactive login, bypassing silent check-sso');
1481
+ await doLogin(true);
1482
+ return;
1483
+ }
1447
1484
  switch (initOptions.onLoad) {
1448
1485
  case 'check-sso':
1449
1486
  if (__classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable) {
@@ -1575,13 +1612,38 @@ async function _TideCloak_checkLoginIframe() {
1575
1612
  */
1576
1613
  async function _TideCloak_checkSsoSilently() {
1577
1614
  const iframe = document.createElement('iframe');
1578
- const src = await this.createLoginUrl({ prompt: 'none', redirectUri: this.silentCheckSsoRedirectUri });
1579
- iframe.setAttribute('src', src);
1580
- iframe.setAttribute('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin');
1581
- iframe.setAttribute('title', 'keycloak-silent-check-sso');
1582
- iframe.style.display = 'none';
1583
- document.body.appendChild(iframe);
1584
- return await new Promise((resolve, reject) => {
1615
+ return await new Promise((resolve) => {
1616
+ let settled = false;
1617
+ /** @type {ReturnType<typeof setTimeout>=} */
1618
+ let timer;
1619
+ /** @type {ReturnType<typeof setTimeout>=} */
1620
+ let loadGraceTimer;
1621
+ const cleanup = () => {
1622
+ if (timer)
1623
+ clearTimeout(timer);
1624
+ if (loadGraceTimer)
1625
+ clearTimeout(loadGraceTimer);
1626
+ window.removeEventListener('message', messageCallback);
1627
+ iframe.removeEventListener('error', onIframeError);
1628
+ iframe.removeEventListener('load', onIframeLoad);
1629
+ if (iframe.parentNode)
1630
+ document.body.removeChild(iframe);
1631
+ };
1632
+ // Every NON-authenticating terminal outcome funnels through here exactly
1633
+ // once: onerror / onabort / load-without-message / timeout /
1634
+ // createLoginUrl-failure. Resolving (never rejecting) as NOT-authenticated
1635
+ // lets init() complete cleanly so the app falls through to interactive
1636
+ // login. This is what makes the silent path structurally un-hangable: no
1637
+ // matter which DOM event (or none) the aborted iframe produces, one of
1638
+ // these fires. `via` is logged so QA can prove which path was taken.
1639
+ const settleNotAuthenticated = (via) => {
1640
+ if (settled)
1641
+ return;
1642
+ settled = true;
1643
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-SILENTSSO] terminal=' + via + ' -> not-authenticated (authenticated=' + this.authenticated + ')');
1644
+ cleanup();
1645
+ resolve();
1646
+ };
1585
1647
  /**
1586
1648
  * @param {MessageEvent} event
1587
1649
  */
@@ -1589,18 +1651,63 @@ async function _TideCloak_checkSsoSilently() {
1589
1651
  if (event.origin !== window.location.origin || iframe.contentWindow !== event.source) {
1590
1652
  return;
1591
1653
  }
1592
- const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.data);
1654
+ if (settled)
1655
+ return;
1656
+ settled = true;
1657
+ cleanup();
1593
1658
  try {
1659
+ const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.data);
1594
1660
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, oauth);
1661
+ __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDE-SILENTSSO] terminal=message -> processed (authenticated=' + this.authenticated + ')');
1595
1662
  resolve();
1596
1663
  }
1597
1664
  catch (error) {
1598
- reject(error);
1665
+ // A failed silent callback must NOT reject: rejecting would surface as
1666
+ // an init() error / AuthWall wall instead of a clean fall-through to
1667
+ // interactive login. Resolve as not-authenticated.
1668
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-SILENTSSO] terminal=message-error -> not-authenticated: ' + (error instanceof Error ? error.message : String(error)));
1669
+ resolve();
1599
1670
  }
1600
- document.body.removeChild(iframe);
1601
- window.removeEventListener('message', messageCallback);
1602
1671
  };
1672
+ const onIframeError = () => settleNotAuthenticated('onerror');
1673
+ const onIframeLoad = () => {
1674
+ // The iframe finished navigating. On the happy path it landed on the
1675
+ // real silent-check-sso.html and `messageCallback` settles first. If it
1676
+ // loaded an error page, or a cross-origin KC page that never posts, or
1677
+ // an aborted navigation that still fired `load`, no message arrives - so
1678
+ // arm a short grace timer that settles NOT-authenticated well before the
1679
+ // outer ceiling.
1680
+ if (settled)
1681
+ return;
1682
+ const grace = Math.max(250, Math.min(2000, Math.floor(this.silentCheckSsoTimeout / 4)));
1683
+ if (loadGraceTimer)
1684
+ clearTimeout(loadGraceTimer);
1685
+ loadGraceTimer = setTimeout(() => settleNotAuthenticated('load-no-message'), grace);
1686
+ };
1687
+ // Wire ALL listeners before the navigation starts so no immediate event is
1688
+ // missed. `error` covers the browsers that surface a failed iframe
1689
+ // navigation as an error/abort event; `load` + the grace timer cover the
1690
+ // ones that fire load on an error document; the outer `timer` covers
1691
+ // net::ERR_ABORTED that produces NO DOM event at all.
1603
1692
  window.addEventListener('message', messageCallback);
1693
+ iframe.addEventListener('error', onIframeError);
1694
+ iframe.addEventListener('load', onIframeLoad);
1695
+ // Hard ceiling. Created synchronously (NOT after an awaited createLoginUrl)
1696
+ // so it is always armed regardless of what the iframe navigation does.
1697
+ timer = setTimeout(() => settleNotAuthenticated('timeout'), this.silentCheckSsoTimeout);
1698
+ // Build the URL and start the navigation. Failure here must still settle
1699
+ // (not reject) so init() stays deterministic.
1700
+ this.createLoginUrl({ prompt: 'none', redirectUri: this.silentCheckSsoRedirectUri })
1701
+ .then((src) => {
1702
+ if (settled)
1703
+ return;
1704
+ iframe.setAttribute('src', src);
1705
+ iframe.setAttribute('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin');
1706
+ iframe.setAttribute('title', 'keycloak-silent-check-sso');
1707
+ iframe.style.display = 'none';
1708
+ document.body.appendChild(iframe);
1709
+ })
1710
+ .catch(() => settleNotAuthenticated('createLoginUrl-error'));
1604
1711
  });
1605
1712
  }, _TideCloak_parseCallback = function _TideCloak_parseCallback(url) {
1606
1713
  const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallbackUrl).call(this, url);
@@ -1735,6 +1842,25 @@ async function _TideCloak_checkSsoSilently() {
1735
1842
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_scheduleCheckIframe).call(this);
1736
1843
  }
1737
1844
  }
1845
+ }, _TideCloak_consumePostLogoutMarker = function _TideCloak_consumePostLogoutMarker() {
1846
+ try {
1847
+ if (typeof window === 'undefined' || !window.localStorage)
1848
+ return false;
1849
+ const raw = window.localStorage.getItem(POST_LOGOUT_MARKER_KEY);
1850
+ if (!raw)
1851
+ return false;
1852
+ window.localStorage.removeItem(POST_LOGOUT_MARKER_KEY);
1853
+ const ts = Number(raw);
1854
+ const fresh = Number.isFinite(ts) && (Date.now() - ts) < POST_LOGOUT_MARKER_TTL_MS;
1855
+ if (!fresh) {
1856
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] marker present but stale (age > ' + POST_LOGOUT_MARKER_TTL_MS + 'ms); ignoring');
1857
+ }
1858
+ return fresh;
1859
+ }
1860
+ catch (e) {
1861
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] could not read marker: ' + (e instanceof Error ? e.message : String(e)));
1862
+ return false;
1863
+ }
1738
1864
  }, _TideCloak_fetchAccessToken =
1739
1865
  /**
1740
1866
  * @typedef {Object} AccessTokenResponse The successful token response from the authorization server, based on the {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 OAuth 2.0 Authorization Framework specification}.
@@ -1842,9 +1968,42 @@ async function _TideCloak_fetchRefreshToken(url, refreshToken, clientId) {
1842
1968
  throw error;
1843
1969
  }
1844
1970
  }, _TideCloak_ensureRequestEnclaveOpen = function _TideCloak_ensureRequestEnclaveOpen() {
1845
- if (!this.requestEnclave)
1971
+ // Never (re)open the enclave without a doken. Post-logout the doken is
1972
+ // flushed; reopening a dokenless hidden enclave wedges it with
1973
+ // "Expecting doken in request".
1974
+ if (!this.requestEnclave || !this.doken)
1846
1975
  return;
1847
1976
  this.requestEnclave.checkEnclaveOpen();
1977
+ }, _TideCloak_reloginInteractively =
1978
+ /**
1979
+ * Trigger a full interactive login redirect. Used as the recovery leg when a
1980
+ * silent flow cannot proceed (e.g. the enclave asks for a doken refresh but
1981
+ * the doken was flushed at logout). Navigates the whole page to Keycloak.
1982
+ * @returns {Promise<void>}
1983
+ */
1984
+ async function _TideCloak_reloginInteractively() {
1985
+ await this.login({
1986
+ idpHint: 'tide',
1987
+ prompt: 'login',
1988
+ redirectUri: window.location.href
1989
+ });
1990
+ }, _TideCloak_provideDokenOrRelogin =
1991
+ /**
1992
+ * Provide the current doken to the enclave, or fall through to interactive
1993
+ * login when there is none (post-logout / flushed state). Throwing a bare
1994
+ * error here would leave the enclave (and whatever awaits its doken-refresh
1995
+ * completion) hanging; instead we redirect to a full interactive login, which
1996
+ * recovers cleanly.
1997
+ * @returns {Promise<string>}
1998
+ */
1999
+ async function _TideCloak_provideDokenOrRelogin() {
2000
+ await this.ensureTokenReady();
2001
+ if (!this.doken) {
2002
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDECLOAK] Enclave requested a doken refresh but no doken is present (post-logout). Falling through to interactive login.');
2003
+ await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this);
2004
+ throw new Error('[TIDECLOAK] No doken found - redirecting to interactive login');
2005
+ }
2006
+ return this.doken;
1848
2007
  }, _TideCloak_getVoucherUrl = function _TideCloak_getVoucherUrl() {
1849
2008
  if (!this.tokenParsed)
1850
2009
  throw new Error('User authentication required to access voucher service');
@@ -1918,9 +2077,32 @@ async function _TideCloak_fetchRefreshToken(url, refreshToken, clientId) {
1918
2077
  else {
1919
2078
  delete this.doken;
1920
2079
  delete this.dokenParsed;
1921
- if (this.requestEnclave && typeof this.requestEnclave.updateDoken === 'function') {
1922
- this.requestEnclave.updateDoken(undefined);
2080
+ // No doken present (e.g. post-logout / flushed state). Do NOT push a
2081
+ // "doken refresh" into the enclave: refreshing with an undefined doken
2082
+ // wedges the hidden enclave ("Expecting doken in request",
2083
+ // TIDE-SWE-UNHANDLED) and blocks the silent re-auth that runs after
2084
+ // logout. Instead tear the stale enclave(s) down so (a) no dokenless
2085
+ // refresh is ever sent, (b) the persistent user-gesture listener can no
2086
+ // longer reopen a dokenless hidden enclave (#ensureRequestEnclaveOpen
2087
+ // no-ops once requestEnclave is cleared), and (c) the next authenticated
2088
+ // flow re-creates a fresh enclave from a valid doken via
2089
+ // initRequestEnclave(). The app is then free to fall through to an
2090
+ // interactive login, which recovers cleanly.
2091
+ __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_teardownEnclaves).call(this);
2092
+ }
2093
+ }, _TideCloak_teardownEnclaves = function _TideCloak_teardownEnclaves() {
2094
+ for (const key of ['requestEnclave', 'approvalEnclave']) {
2095
+ const enclave = this[key];
2096
+ if (!enclave)
2097
+ continue;
2098
+ try {
2099
+ if (typeof enclave.close === 'function')
2100
+ enclave.close();
2101
+ }
2102
+ catch (error) {
2103
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDECLOAK] Failed to close ' + key + ': ' + (error instanceof Error ? error.message : error));
1923
2104
  }
2105
+ this[key] = undefined;
1924
2106
  }
1925
2107
  }, _TideCloak_getRealmUrl = function _TideCloak_getRealmUrl() {
1926
2108
  if (typeof this.authServerUrl === 'undefined') {