@tidecloak/js 0.13.33 → 0.13.35-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.
@@ -36,10 +36,18 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
36
36
  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");
37
37
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
38
38
  };
39
- 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;
39
+ 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;
40
40
  // MODIFIED: Added dependency to external Tide helper libraries.
41
41
  import { RequestEnclave, ApprovalEnclave, ApprovalEnclaveNew } from "heimdall-tide";
42
42
  const CONTENT_TYPE_JSON = 'application/json';
43
+ // Post-logout marker. After an explicit logout there is no session to silently
44
+ // resume, so the immediately-following init() must skip the racy silent
45
+ // check-sso (whose hidden iframe can net::ERR_ABORTED and wedge the app) and go
46
+ // straight to interactive login. `logout()` stamps this durable localStorage
47
+ // marker (survives the logout redirect round-trip); init() consumes it exactly
48
+ // once and forces an interactive login when present + fresh.
49
+ const POST_LOGOUT_MARKER_KEY = 'tide-post-logout';
50
+ const POST_LOGOUT_MARKER_TTL_MS = 60000;
43
51
  /**
44
52
  * @typedef {Object} Endpoints
45
53
  * @property {() => string} authorize
@@ -107,6 +115,15 @@ class TideCloak {
107
115
  this.timeSkew = null;
108
116
  /** @type {boolean} */
109
117
  this.silentCheckSsoFallback = true;
118
+ /**
119
+ * Max time (ms) to wait for the hidden silent-check-sso iframe to post back
120
+ * before treating the attempt as "not authenticated". Guards against the
121
+ * post-logout wedge where the check-sso (or the Tide enclave it spins up)
122
+ * never responds and init() would otherwise hang forever. Overridable via
123
+ * initOptions.silentCheckSsoTimeout.
124
+ * @type {number}
125
+ */
126
+ this.silentCheckSsoTimeout = 10000;
110
127
  /** @type {TideCloakPkceMethod} */
111
128
  this.pkceMethod = 'S256';
112
129
  this.enableLogging = false;
@@ -185,6 +202,9 @@ class TideCloak {
185
202
  if (typeof initOptions.silentCheckSsoFallback === 'boolean') {
186
203
  this.silentCheckSsoFallback = initOptions.silentCheckSsoFallback;
187
204
  }
205
+ if (typeof initOptions.silentCheckSsoTimeout === 'number' && initOptions.silentCheckSsoTimeout > 0) {
206
+ this.silentCheckSsoTimeout = initOptions.silentCheckSsoTimeout;
207
+ }
188
208
  if (typeof initOptions.pkceMethod !== 'undefined') {
189
209
  if (initOptions.pkceMethod !== 'S256' && initOptions.pkceMethod !== false) {
190
210
  throw new TypeError(`Invalid value for pkceMethod', expected 'S256' or false but got ${initOptions.pkceMethod}.`);
@@ -231,9 +251,29 @@ class TideCloak {
231
251
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_check3pCookiesSupported).call(this);
232
252
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processInit).call(this, initOptions);
233
253
  (_c = this.onReady) === null || _c === void 0 ? void 0 : _c.call(this, this.authenticated);
234
- // initialize request enclave if authenticated
235
- if (this.doken && initOptions.setupRequestEnclave) {
236
- this.initRequestEnclave();
254
+ // Do NOT eagerly open the Tide RequestEnclave during init().
255
+ //
256
+ // On a post-logout silent re-auth, the doken returned by the silent
257
+ // check-sso can be STALE: its session key no longer matches the session the
258
+ // hidden enclave cached from the previous login. Opening the hidden enclave
259
+ // here makes it sit "waiting for doken refresh" forever - a refresh that
260
+ // never comes because no fresh doken exists - which is exactly the
261
+ // "[ENCLAVE] Received init but waiting for doken refresh" ... hang that
262
+ // wedges the SPA on "Signing you in..." on the second login. (The enclave
263
+ // silently WAITS rather than emitting an error, so heimdall's own
264
+ // requireReloginCallback recovery never fires.)
265
+ //
266
+ // Instead we DEFER enclave setup: it is created lazily on the first real
267
+ // Tide operation (encrypt / decrypt / approve / signDpopApproval - each
268
+ // calls initRequestEnclave()), by which point a fresh interactive login has
269
+ // minted a current doken and the enclave can complete its handshake. We
270
+ // still register the user-gesture listener so the popup fallback can open
271
+ // once an enclave has actually been created; #ensureRequestEnclaveOpen
272
+ // no-ops while there is no enclave or no doken.
273
+ if (initOptions.setupRequestEnclave) {
274
+ if (this.doken) {
275
+ __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).');
276
+ }
237
277
  // to get around popups requiring user gestures
238
278
  document.addEventListener('click', () => {
239
279
  __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_ensureRequestEnclaveOpen).call(this);
@@ -337,6 +377,16 @@ class TideCloak {
337
377
  this.logout = async (options) => {
338
378
  var _a;
339
379
  await ((_a = __classPrivateFieldGet(this, _TideCloak_dpopProvider, "f")) === null || _a === void 0 ? void 0 : _a.flush());
380
+ // Stamp the post-logout marker so the next init() forces interactive login
381
+ // instead of running the racy silent check-sso. See POST_LOGOUT_MARKER_KEY.
382
+ try {
383
+ if (typeof window !== 'undefined' && window.localStorage) {
384
+ window.localStorage.setItem(POST_LOGOUT_MARKER_KEY, Date.now().toString());
385
+ }
386
+ }
387
+ catch (e) {
388
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] could not set marker: ' + (e instanceof Error ? e.message : String(e)));
389
+ }
340
390
  return __classPrivateFieldGet(this, _TideCloak_adapter, "f").logout(options);
341
391
  };
342
392
  /**
@@ -625,19 +675,8 @@ class TideCloak {
625
675
  isRunningLocal: new URL(__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getVoucherUrl).call(this)).hostname === "localhost"
626
676
  }).init({
627
677
  doken: this.doken,
628
- dokenRefreshCallback: async () => {
629
- await this.ensureTokenReady();
630
- if (!this.doken)
631
- throw new Error('[TIDECLOAK] No doken found');
632
- return this.doken;
633
- },
634
- requireReloginCallback: async () => {
635
- await this.login({
636
- idpHint: 'tide',
637
- prompt: 'login',
638
- redirectUri: window.location.href
639
- });
640
- }
678
+ dokenRefreshCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_provideDokenOrRelogin).call(this),
679
+ requireReloginCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this)
641
680
  });
642
681
  }
643
682
  }
@@ -659,19 +698,8 @@ class TideCloak {
659
698
  backgroundUrl: __classPrivateFieldGet(this, _TideCloak_config, "f")['backgroundUrl'],
660
699
  logoUrl: __classPrivateFieldGet(this, _TideCloak_config, "f")['logoUrl'],
661
700
  doken: this.doken,
662
- dokenRefreshCallback: async () => {
663
- await this.ensureTokenReady();
664
- if (!this.doken)
665
- throw new Error('[TIDECLOAK] No doken found');
666
- return this.doken;
667
- },
668
- requireReloginCallback: async () => {
669
- await this.login({
670
- idpHint: 'tide',
671
- prompt: 'login',
672
- redirectUri: window.location.href
673
- });
674
- }
701
+ dokenRefreshCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_provideDokenOrRelogin).call(this),
702
+ requireReloginCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this)
675
703
  });
676
704
  }
677
705
  }
@@ -1402,6 +1430,15 @@ async function _TideCloak_processInit(initOptions) {
1402
1430
  await this.login(options);
1403
1431
  };
1404
1432
  const onLoad = async () => {
1433
+ // Post-logout deterministic path: if the previous logout stamped a fresh
1434
+ // marker, skip silent check-sso entirely and go straight to interactive
1435
+ // login. There is no session to silently resume after an explicit logout,
1436
+ // and the silent path is exactly where the ERR_ABORTED wedge lives.
1437
+ if (__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_consumePostLogoutMarker).call(this)) {
1438
+ __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDE-POSTLOGOUT] marker honored in init(); forcing interactive login, bypassing silent check-sso');
1439
+ await doLogin(true);
1440
+ return;
1441
+ }
1405
1442
  switch (initOptions.onLoad) {
1406
1443
  case 'check-sso':
1407
1444
  if (__classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable) {
@@ -1533,13 +1570,38 @@ async function _TideCloak_checkLoginIframe() {
1533
1570
  */
1534
1571
  async function _TideCloak_checkSsoSilently() {
1535
1572
  const iframe = document.createElement('iframe');
1536
- const src = await this.createLoginUrl({ prompt: 'none', redirectUri: this.silentCheckSsoRedirectUri });
1537
- iframe.setAttribute('src', src);
1538
- iframe.setAttribute('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin');
1539
- iframe.setAttribute('title', 'keycloak-silent-check-sso');
1540
- iframe.style.display = 'none';
1541
- document.body.appendChild(iframe);
1542
- return await new Promise((resolve, reject) => {
1573
+ return await new Promise((resolve) => {
1574
+ let settled = false;
1575
+ /** @type {ReturnType<typeof setTimeout>=} */
1576
+ let timer;
1577
+ /** @type {ReturnType<typeof setTimeout>=} */
1578
+ let loadGraceTimer;
1579
+ const cleanup = () => {
1580
+ if (timer)
1581
+ clearTimeout(timer);
1582
+ if (loadGraceTimer)
1583
+ clearTimeout(loadGraceTimer);
1584
+ window.removeEventListener('message', messageCallback);
1585
+ iframe.removeEventListener('error', onIframeError);
1586
+ iframe.removeEventListener('load', onIframeLoad);
1587
+ if (iframe.parentNode)
1588
+ document.body.removeChild(iframe);
1589
+ };
1590
+ // Every NON-authenticating terminal outcome funnels through here exactly
1591
+ // once: onerror / onabort / load-without-message / timeout /
1592
+ // createLoginUrl-failure. Resolving (never rejecting) as NOT-authenticated
1593
+ // lets init() complete cleanly so the app falls through to interactive
1594
+ // login. This is what makes the silent path structurally un-hangable: no
1595
+ // matter which DOM event (or none) the aborted iframe produces, one of
1596
+ // these fires. `via` is logged so QA can prove which path was taken.
1597
+ const settleNotAuthenticated = (via) => {
1598
+ if (settled)
1599
+ return;
1600
+ settled = true;
1601
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-SILENTSSO] terminal=' + via + ' -> not-authenticated (authenticated=' + this.authenticated + ')');
1602
+ cleanup();
1603
+ resolve();
1604
+ };
1543
1605
  /**
1544
1606
  * @param {MessageEvent} event
1545
1607
  */
@@ -1547,18 +1609,63 @@ async function _TideCloak_checkSsoSilently() {
1547
1609
  if (event.origin !== window.location.origin || iframe.contentWindow !== event.source) {
1548
1610
  return;
1549
1611
  }
1550
- const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.data);
1612
+ if (settled)
1613
+ return;
1614
+ settled = true;
1615
+ cleanup();
1551
1616
  try {
1617
+ const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.data);
1552
1618
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, oauth);
1619
+ __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDE-SILENTSSO] terminal=message -> processed (authenticated=' + this.authenticated + ')');
1553
1620
  resolve();
1554
1621
  }
1555
1622
  catch (error) {
1556
- reject(error);
1623
+ // A failed silent callback must NOT reject: rejecting would surface as
1624
+ // an init() error / AuthWall wall instead of a clean fall-through to
1625
+ // interactive login. Resolve as not-authenticated.
1626
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-SILENTSSO] terminal=message-error -> not-authenticated: ' + (error instanceof Error ? error.message : String(error)));
1627
+ resolve();
1557
1628
  }
1558
- document.body.removeChild(iframe);
1559
- window.removeEventListener('message', messageCallback);
1560
1629
  };
1630
+ const onIframeError = () => settleNotAuthenticated('onerror');
1631
+ const onIframeLoad = () => {
1632
+ // The iframe finished navigating. On the happy path it landed on the
1633
+ // real silent-check-sso.html and `messageCallback` settles first. If it
1634
+ // loaded an error page, or a cross-origin KC page that never posts, or
1635
+ // an aborted navigation that still fired `load`, no message arrives - so
1636
+ // arm a short grace timer that settles NOT-authenticated well before the
1637
+ // outer ceiling.
1638
+ if (settled)
1639
+ return;
1640
+ const grace = Math.max(250, Math.min(2000, Math.floor(this.silentCheckSsoTimeout / 4)));
1641
+ if (loadGraceTimer)
1642
+ clearTimeout(loadGraceTimer);
1643
+ loadGraceTimer = setTimeout(() => settleNotAuthenticated('load-no-message'), grace);
1644
+ };
1645
+ // Wire ALL listeners before the navigation starts so no immediate event is
1646
+ // missed. `error` covers the browsers that surface a failed iframe
1647
+ // navigation as an error/abort event; `load` + the grace timer cover the
1648
+ // ones that fire load on an error document; the outer `timer` covers
1649
+ // net::ERR_ABORTED that produces NO DOM event at all.
1561
1650
  window.addEventListener('message', messageCallback);
1651
+ iframe.addEventListener('error', onIframeError);
1652
+ iframe.addEventListener('load', onIframeLoad);
1653
+ // Hard ceiling. Created synchronously (NOT after an awaited createLoginUrl)
1654
+ // so it is always armed regardless of what the iframe navigation does.
1655
+ timer = setTimeout(() => settleNotAuthenticated('timeout'), this.silentCheckSsoTimeout);
1656
+ // Build the URL and start the navigation. Failure here must still settle
1657
+ // (not reject) so init() stays deterministic.
1658
+ this.createLoginUrl({ prompt: 'none', redirectUri: this.silentCheckSsoRedirectUri })
1659
+ .then((src) => {
1660
+ if (settled)
1661
+ return;
1662
+ iframe.setAttribute('src', src);
1663
+ iframe.setAttribute('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin');
1664
+ iframe.setAttribute('title', 'keycloak-silent-check-sso');
1665
+ iframe.style.display = 'none';
1666
+ document.body.appendChild(iframe);
1667
+ })
1668
+ .catch(() => settleNotAuthenticated('createLoginUrl-error'));
1562
1669
  });
1563
1670
  }, _TideCloak_parseCallback = function _TideCloak_parseCallback(url) {
1564
1671
  const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallbackUrl).call(this, url);
@@ -1693,6 +1800,25 @@ async function _TideCloak_checkSsoSilently() {
1693
1800
  await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_scheduleCheckIframe).call(this);
1694
1801
  }
1695
1802
  }
1803
+ }, _TideCloak_consumePostLogoutMarker = function _TideCloak_consumePostLogoutMarker() {
1804
+ try {
1805
+ if (typeof window === 'undefined' || !window.localStorage)
1806
+ return false;
1807
+ const raw = window.localStorage.getItem(POST_LOGOUT_MARKER_KEY);
1808
+ if (!raw)
1809
+ return false;
1810
+ window.localStorage.removeItem(POST_LOGOUT_MARKER_KEY);
1811
+ const ts = Number(raw);
1812
+ const fresh = Number.isFinite(ts) && (Date.now() - ts) < POST_LOGOUT_MARKER_TTL_MS;
1813
+ if (!fresh) {
1814
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] marker present but stale (age > ' + POST_LOGOUT_MARKER_TTL_MS + 'ms); ignoring');
1815
+ }
1816
+ return fresh;
1817
+ }
1818
+ catch (e) {
1819
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] could not read marker: ' + (e instanceof Error ? e.message : String(e)));
1820
+ return false;
1821
+ }
1696
1822
  }, _TideCloak_fetchAccessToken =
1697
1823
  /**
1698
1824
  * @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}.
@@ -1800,9 +1926,42 @@ async function _TideCloak_fetchRefreshToken(url, refreshToken, clientId) {
1800
1926
  throw error;
1801
1927
  }
1802
1928
  }, _TideCloak_ensureRequestEnclaveOpen = function _TideCloak_ensureRequestEnclaveOpen() {
1803
- if (!this.requestEnclave)
1929
+ // Never (re)open the enclave without a doken. Post-logout the doken is
1930
+ // flushed; reopening a dokenless hidden enclave wedges it with
1931
+ // "Expecting doken in request".
1932
+ if (!this.requestEnclave || !this.doken)
1804
1933
  return;
1805
1934
  this.requestEnclave.checkEnclaveOpen();
1935
+ }, _TideCloak_reloginInteractively =
1936
+ /**
1937
+ * Trigger a full interactive login redirect. Used as the recovery leg when a
1938
+ * silent flow cannot proceed (e.g. the enclave asks for a doken refresh but
1939
+ * the doken was flushed at logout). Navigates the whole page to Keycloak.
1940
+ * @returns {Promise<void>}
1941
+ */
1942
+ async function _TideCloak_reloginInteractively() {
1943
+ await this.login({
1944
+ idpHint: 'tide',
1945
+ prompt: 'login',
1946
+ redirectUri: window.location.href
1947
+ });
1948
+ }, _TideCloak_provideDokenOrRelogin =
1949
+ /**
1950
+ * Provide the current doken to the enclave, or fall through to interactive
1951
+ * login when there is none (post-logout / flushed state). Throwing a bare
1952
+ * error here would leave the enclave (and whatever awaits its doken-refresh
1953
+ * completion) hanging; instead we redirect to a full interactive login, which
1954
+ * recovers cleanly.
1955
+ * @returns {Promise<string>}
1956
+ */
1957
+ async function _TideCloak_provideDokenOrRelogin() {
1958
+ await this.ensureTokenReady();
1959
+ if (!this.doken) {
1960
+ __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.');
1961
+ await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this);
1962
+ throw new Error('[TIDECLOAK] No doken found - redirecting to interactive login');
1963
+ }
1964
+ return this.doken;
1806
1965
  }, _TideCloak_getVoucherUrl = function _TideCloak_getVoucherUrl() {
1807
1966
  if (!this.tokenParsed)
1808
1967
  throw new Error('User authentication required to access voucher service');
@@ -1876,9 +2035,32 @@ async function _TideCloak_fetchRefreshToken(url, refreshToken, clientId) {
1876
2035
  else {
1877
2036
  delete this.doken;
1878
2037
  delete this.dokenParsed;
1879
- if (this.requestEnclave && typeof this.requestEnclave.updateDoken === 'function') {
1880
- this.requestEnclave.updateDoken(undefined);
2038
+ // No doken present (e.g. post-logout / flushed state). Do NOT push a
2039
+ // "doken refresh" into the enclave: refreshing with an undefined doken
2040
+ // wedges the hidden enclave ("Expecting doken in request",
2041
+ // TIDE-SWE-UNHANDLED) and blocks the silent re-auth that runs after
2042
+ // logout. Instead tear the stale enclave(s) down so (a) no dokenless
2043
+ // refresh is ever sent, (b) the persistent user-gesture listener can no
2044
+ // longer reopen a dokenless hidden enclave (#ensureRequestEnclaveOpen
2045
+ // no-ops once requestEnclave is cleared), and (c) the next authenticated
2046
+ // flow re-creates a fresh enclave from a valid doken via
2047
+ // initRequestEnclave(). The app is then free to fall through to an
2048
+ // interactive login, which recovers cleanly.
2049
+ __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_teardownEnclaves).call(this);
2050
+ }
2051
+ }, _TideCloak_teardownEnclaves = function _TideCloak_teardownEnclaves() {
2052
+ for (const key of ['requestEnclave', 'approvalEnclave']) {
2053
+ const enclave = this[key];
2054
+ if (!enclave)
2055
+ continue;
2056
+ try {
2057
+ if (typeof enclave.close === 'function')
2058
+ enclave.close();
2059
+ }
2060
+ catch (error) {
2061
+ __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDECLOAK] Failed to close ' + key + ': ' + (error instanceof Error ? error.message : error));
1881
2062
  }
2063
+ this[key] = undefined;
1882
2064
  }
1883
2065
  }, _TideCloak_getRealmUrl = function _TideCloak_getRealmUrl() {
1884
2066
  if (typeof this.authServerUrl === 'undefined') {