@civic/auth 0.9.1-beta.2 โ 0.9.1-beta.4
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/CHANGELOG.md +1 -0
- package/dist/lib/oauth.d.ts +2 -1
- package/dist/lib/oauth.d.ts.map +1 -1
- package/dist/lib/oauth.js +1 -1
- package/dist/lib/oauth.js.map +1 -1
- package/dist/nextjs/config.d.ts +1 -0
- package/dist/nextjs/config.d.ts.map +1 -1
- package/dist/react-router-7/components/UserButton.d.ts.map +1 -1
- package/dist/react-router-7/components/UserButton.js +3 -1
- package/dist/react-router-7/components/UserButton.js.map +1 -1
- package/dist/react-router-7/cookies.d.ts +1 -1
- package/dist/react-router-7/cookies.d.ts.map +1 -1
- package/dist/react-router-7/cookies.js +3 -9
- package/dist/react-router-7/cookies.js.map +1 -1
- package/dist/react-router-7/useUser.d.ts +0 -2
- package/dist/react-router-7/useUser.d.ts.map +1 -1
- package/dist/react-router-7/useUser.js +1 -11
- package/dist/react-router-7/useUser.js.map +1 -1
- package/dist/server/config.d.ts +1 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/vanillajs/auth/SessionManager.d.ts +9 -1
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +103 -47
- package/dist/vanillajs/auth/SessionManager.js.map +1 -1
- package/package.json +2 -1
- package/dist/vanillajs/auth/handlers/LogoutHandler.d.ts +0 -57
- package/dist/vanillajs/auth/handlers/LogoutHandler.d.ts.map +0 -1
- package/dist/vanillajs/auth/handlers/LogoutHandler.js +0 -246
- package/dist/vanillajs/auth/handlers/LogoutHandler.js.map +0 -1
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { IframeManager } from "../../iframe/IframeManager.js";
|
|
2
|
-
import { CivicAuthError, CivicAuthErrorCode } from "../types/AuthTypes.js";
|
|
3
|
-
import { createLogger as createLoggerFn } from "../../utils/logger.js";
|
|
4
|
-
/**
|
|
5
|
-
* LogoutHandler - Manages logout iframe using the same infrastructure as login
|
|
6
|
-
* Handles iframe creation, navigation, cleanup, and error handling for logout flows
|
|
7
|
-
*/
|
|
8
|
-
export class LogoutHandler {
|
|
9
|
-
config;
|
|
10
|
-
logger = createLoggerFn("logout-handler");
|
|
11
|
-
onLogoutComplete;
|
|
12
|
-
onLogoutError;
|
|
13
|
-
cleanup;
|
|
14
|
-
iframeManager;
|
|
15
|
-
iframeElement;
|
|
16
|
-
constructor(handlerConfig) {
|
|
17
|
-
this.config = handlerConfig.config;
|
|
18
|
-
this.logger = handlerConfig.logger;
|
|
19
|
-
this.onLogoutComplete = handlerConfig.onLogoutComplete;
|
|
20
|
-
this.onLogoutError = handlerConfig.onLogoutError;
|
|
21
|
-
this.cleanup = handlerConfig.cleanup;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Handles logout using a hidden iframe similar to how login handles authentication
|
|
25
|
-
*/
|
|
26
|
-
async handleLogoutIframe(logoutUrl) {
|
|
27
|
-
this.logger.info("๐ช Creating logout iframe using IframeManager", {
|
|
28
|
-
url: logoutUrl,
|
|
29
|
-
});
|
|
30
|
-
try {
|
|
31
|
-
// Create a hidden container for the logout iframe
|
|
32
|
-
const container = this.createHiddenContainer();
|
|
33
|
-
// Create IframeManager in modal mode but keep it hidden
|
|
34
|
-
this.iframeManager = new IframeManager({
|
|
35
|
-
container: container,
|
|
36
|
-
displayMode: "modal",
|
|
37
|
-
iframeId: `${this.config.iframeId || "civic-auth-iframe"}-logout`,
|
|
38
|
-
onClose: () => {
|
|
39
|
-
this.logger.debug("Logout iframe close requested (should not happen for hidden iframe)");
|
|
40
|
-
// For logout, we don't expect user interaction, but handle it gracefully
|
|
41
|
-
this.cleanupLogoutIframe();
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
// Create the iframe using IframeManager
|
|
45
|
-
this.iframeElement = this.iframeManager.createIframe(logoutUrl);
|
|
46
|
-
// Keep the iframe hidden since this is a background logout
|
|
47
|
-
this.iframeManager.hide();
|
|
48
|
-
// Set up event handlers for the logout iframe
|
|
49
|
-
this.setupLogoutIframeEventHandlers();
|
|
50
|
-
this.setupLogoutNavigationMonitoring();
|
|
51
|
-
this.logger.info("โ
Logout iframe created successfully", {
|
|
52
|
-
url: logoutUrl,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
const errorMessage = error instanceof Error
|
|
57
|
-
? error.message
|
|
58
|
-
: "Failed to create logout iframe";
|
|
59
|
-
this.logger.error("โ Failed to create logout iframe", {
|
|
60
|
-
error: errorMessage,
|
|
61
|
-
});
|
|
62
|
-
this.onLogoutError(new CivicAuthError(errorMessage, CivicAuthErrorCode.LOGOUT_FAILED));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Creates a hidden container element for the logout iframe
|
|
67
|
-
*/
|
|
68
|
-
createHiddenContainer() {
|
|
69
|
-
const container = document.createElement("div");
|
|
70
|
-
container.id = "civic-logout-iframe-container";
|
|
71
|
-
container.style.display = "none";
|
|
72
|
-
container.style.position = "fixed";
|
|
73
|
-
container.style.left = "-9999px";
|
|
74
|
-
container.style.top = "-9999px";
|
|
75
|
-
container.style.width = "1px";
|
|
76
|
-
container.style.height = "1px";
|
|
77
|
-
container.style.overflow = "hidden";
|
|
78
|
-
container.style.pointerEvents = "none";
|
|
79
|
-
container.style.visibility = "hidden";
|
|
80
|
-
container.style.zIndex = "-1";
|
|
81
|
-
document.body.appendChild(container);
|
|
82
|
-
return container;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Sets up event handlers for the logout iframe
|
|
86
|
-
*/
|
|
87
|
-
setupLogoutIframeEventHandlers() {
|
|
88
|
-
if (!this.iframeElement)
|
|
89
|
-
return;
|
|
90
|
-
this.iframeElement.onload = () => {
|
|
91
|
-
this.logger.info("โ
Logout iframe loaded successfully", {
|
|
92
|
-
iframeSrc: this.iframeElement?.src,
|
|
93
|
-
});
|
|
94
|
-
// Try to detect redirect to our logout redirect URL
|
|
95
|
-
this.checkLogoutIframeRedirect();
|
|
96
|
-
};
|
|
97
|
-
this.iframeElement.onerror = (event) => {
|
|
98
|
-
this.logger.error("โ Logout iframe load error", {
|
|
99
|
-
event,
|
|
100
|
-
iframeSrc: this.iframeElement?.src,
|
|
101
|
-
});
|
|
102
|
-
const error = new CivicAuthError("Logout iframe failed to load", CivicAuthErrorCode.LOGOUT_FAILED);
|
|
103
|
-
this.onLogoutError(error);
|
|
104
|
-
this.cleanupLogoutIframe();
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Cleans up the logout iframe and container
|
|
109
|
-
*/
|
|
110
|
-
cleanupLogoutIframe() {
|
|
111
|
-
this.logger.debug("๐งน Cleaning up logout iframe");
|
|
112
|
-
if (this.iframeManager) {
|
|
113
|
-
// Get the container before cleanup to remove it from DOM
|
|
114
|
-
const container = document.getElementById("civic-logout-iframe-container");
|
|
115
|
-
this.iframeManager.cleanup();
|
|
116
|
-
this.iframeManager = undefined;
|
|
117
|
-
// Remove the hidden container from DOM
|
|
118
|
-
if (container && container.parentNode) {
|
|
119
|
-
container.parentNode.removeChild(container);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (this.iframeElement) {
|
|
123
|
-
this.iframeElement = undefined;
|
|
124
|
-
}
|
|
125
|
-
this.logger.debug("โ
Logout iframe cleanup completed");
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Get the current iframe element (for testing/debugging)
|
|
129
|
-
*/
|
|
130
|
-
getIframeElement() {
|
|
131
|
-
return this.iframeElement;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Check if logout iframe is currently active
|
|
135
|
-
*/
|
|
136
|
-
isLogoutActive() {
|
|
137
|
-
return !!(this.iframeManager && this.iframeElement);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Checks if the logout iframe has redirected to our logout redirect URL
|
|
141
|
-
*/
|
|
142
|
-
checkLogoutIframeRedirect() {
|
|
143
|
-
if (!this.config.logoutRedirectUrl) {
|
|
144
|
-
this.logger.debug("No logoutRedirectUrl configured, treating initial load as completion");
|
|
145
|
-
// If no logout redirect URL is configured, consider the initial load as completion
|
|
146
|
-
this.onLogoutComplete();
|
|
147
|
-
setTimeout(() => {
|
|
148
|
-
this.cleanupLogoutIframe();
|
|
149
|
-
}, 1000);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
const currentIframeHref = this.iframeElement?.contentWindow?.location.href;
|
|
154
|
-
if (currentIframeHref) {
|
|
155
|
-
this.logger.debug("Logout iframe current href accessible", {
|
|
156
|
-
href: currentIframeHref,
|
|
157
|
-
logoutRedirectUrl: this.config.logoutRedirectUrl,
|
|
158
|
-
startsWithLogoutRedirect: currentIframeHref.startsWith(this.config.logoutRedirectUrl),
|
|
159
|
-
});
|
|
160
|
-
if (currentIframeHref.startsWith(this.config.logoutRedirectUrl)) {
|
|
161
|
-
this.logger.info("โ
Logout iframe has navigated to logoutRedirectUrl - logout complete");
|
|
162
|
-
this.onLogoutComplete();
|
|
163
|
-
// Clean up the iframe after a short delay
|
|
164
|
-
setTimeout(() => {
|
|
165
|
-
this.cleanupLogoutIframe();
|
|
166
|
-
}, 500);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
this.logger.debug("Error checking logout iframe href (expected for cross-origin)", {
|
|
172
|
-
error: error instanceof Error ? error.message : String(error),
|
|
173
|
-
iframeSrc: this.iframeElement?.src,
|
|
174
|
-
logoutRedirectUrl: this.config.logoutRedirectUrl,
|
|
175
|
-
});
|
|
176
|
-
// This is expected when the iframe is on the OIDC provider domain
|
|
177
|
-
this.logger.debug("Logout iframe is on OIDC provider domain - using navigation monitoring", {
|
|
178
|
-
parentOrigin: window.location.origin,
|
|
179
|
-
logoutRedirectUrl: this.config.logoutRedirectUrl,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Sets up navigation monitoring to detect when logout iframe redirects to logoutRedirectUrl
|
|
185
|
-
* Similar to login's setupIframeNavigationMonitoring but for logout flow
|
|
186
|
-
*/
|
|
187
|
-
setupLogoutNavigationMonitoring() {
|
|
188
|
-
if (!this.config.logoutRedirectUrl) {
|
|
189
|
-
this.logger.debug("No logoutRedirectUrl configured, skipping navigation monitoring");
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
let monitoringInterval = undefined;
|
|
193
|
-
let lastKnownUrl = "";
|
|
194
|
-
const checkLogoutNavigation = () => {
|
|
195
|
-
if (!this.iframeElement?.contentWindow) {
|
|
196
|
-
if (monitoringInterval) {
|
|
197
|
-
clearInterval(monitoringInterval);
|
|
198
|
-
}
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
const currentUrl = this.iframeElement.contentWindow.location.href;
|
|
203
|
-
if (currentUrl !== lastKnownUrl) {
|
|
204
|
-
lastKnownUrl = currentUrl;
|
|
205
|
-
this.logger.debug("Logout iframe navigation detected", {
|
|
206
|
-
newUrl: currentUrl,
|
|
207
|
-
logoutRedirectUrl: this.config.logoutRedirectUrl,
|
|
208
|
-
isLogoutRedirectUrl: currentUrl.startsWith(this.config.logoutRedirectUrl),
|
|
209
|
-
});
|
|
210
|
-
// Check if iframe has navigated to our logout redirect URL
|
|
211
|
-
if (currentUrl.startsWith(this.config.logoutRedirectUrl)) {
|
|
212
|
-
this.logger.info("โ
Logout iframe navigated to logoutRedirectUrl - logout complete");
|
|
213
|
-
if (monitoringInterval) {
|
|
214
|
-
clearInterval(monitoringInterval);
|
|
215
|
-
}
|
|
216
|
-
this.onLogoutComplete();
|
|
217
|
-
// Clean up the iframe after a short delay
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
this.cleanupLogoutIframe();
|
|
220
|
-
}, 500);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
catch (error) {
|
|
225
|
-
// Expected when iframe is on different origin
|
|
226
|
-
// Only log if we haven't seen this before
|
|
227
|
-
if (lastKnownUrl !== "cross-origin") {
|
|
228
|
-
lastKnownUrl = "cross-origin";
|
|
229
|
-
this.logger.debug("Logout iframe on cross-origin domain (expected during logout flow)", { logoutRedirectUrl: this.config.logoutRedirectUrl });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
// Check immediately and then every 100ms for faster detection
|
|
234
|
-
checkLogoutNavigation();
|
|
235
|
-
monitoringInterval = window.setInterval(checkLogoutNavigation, 100);
|
|
236
|
-
// Store cleanup function to clear monitoring
|
|
237
|
-
const originalCleanup = this.cleanupLogoutIframe.bind(this);
|
|
238
|
-
this.cleanupLogoutIframe = () => {
|
|
239
|
-
if (monitoringInterval) {
|
|
240
|
-
clearInterval(monitoringInterval);
|
|
241
|
-
}
|
|
242
|
-
originalCleanup();
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
//# sourceMappingURL=LogoutHandler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LogoutHandler.js","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/LogoutHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { IframeManager } from \"../../iframe/IframeManager.js\";\nimport type { ProcessedCivicAuthConfig } from \"../types/AuthTypes.js\";\nimport { CivicAuthError, CivicAuthErrorCode } from \"../types/AuthTypes.js\";\nimport type { createLogger } from \"../../utils/logger.js\";\nimport { createLogger as createLoggerFn } from \"../../utils/logger.js\";\n\nexport interface LogoutHandlerConfig {\n config: ProcessedCivicAuthConfig;\n logger: ReturnType<typeof createLogger>;\n onLogoutComplete: () => void;\n onLogoutError: (error: Error) => void;\n cleanup: () => void;\n}\n\n/**\n * LogoutHandler - Manages logout iframe using the same infrastructure as login\n * Handles iframe creation, navigation, cleanup, and error handling for logout flows\n */\nexport class LogoutHandler {\n private config: ProcessedCivicAuthConfig;\n private logger = createLoggerFn(\"logout-handler\");\n private onLogoutComplete: () => void;\n private onLogoutError: (error: Error) => void;\n private cleanup: () => void;\n private iframeManager?: IframeManager;\n private iframeElement?: HTMLIFrameElement;\n\n constructor(handlerConfig: LogoutHandlerConfig) {\n this.config = handlerConfig.config;\n this.logger = handlerConfig.logger;\n this.onLogoutComplete = handlerConfig.onLogoutComplete;\n this.onLogoutError = handlerConfig.onLogoutError;\n this.cleanup = handlerConfig.cleanup;\n }\n\n /**\n * Handles logout using a hidden iframe similar to how login handles authentication\n */\n public async handleLogoutIframe(logoutUrl: string): Promise<void> {\n this.logger.info(\"๐ช Creating logout iframe using IframeManager\", {\n url: logoutUrl,\n });\n\n try {\n // Create a hidden container for the logout iframe\n const container = this.createHiddenContainer();\n\n // Create IframeManager in modal mode but keep it hidden\n this.iframeManager = new IframeManager({\n container: container,\n displayMode: \"modal\",\n iframeId: `${this.config.iframeId || \"civic-auth-iframe\"}-logout`,\n onClose: () => {\n this.logger.debug(\n \"Logout iframe close requested (should not happen for hidden iframe)\",\n );\n // For logout, we don't expect user interaction, but handle it gracefully\n this.cleanupLogoutIframe();\n },\n });\n\n // Create the iframe using IframeManager\n this.iframeElement = this.iframeManager.createIframe(logoutUrl);\n\n // Keep the iframe hidden since this is a background logout\n this.iframeManager.hide();\n\n // Set up event handlers for the logout iframe\n this.setupLogoutIframeEventHandlers();\n this.setupLogoutNavigationMonitoring();\n\n this.logger.info(\"โ
Logout iframe created successfully\", {\n url: logoutUrl,\n });\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : \"Failed to create logout iframe\";\n this.logger.error(\"โ Failed to create logout iframe\", {\n error: errorMessage,\n });\n\n this.onLogoutError(\n new CivicAuthError(errorMessage, CivicAuthErrorCode.LOGOUT_FAILED),\n );\n }\n }\n\n /**\n * Creates a hidden container element for the logout iframe\n */\n private createHiddenContainer(): HTMLElement {\n const container = document.createElement(\"div\");\n container.id = \"civic-logout-iframe-container\";\n container.style.display = \"none\";\n container.style.position = \"fixed\";\n container.style.left = \"-9999px\";\n container.style.top = \"-9999px\";\n container.style.width = \"1px\";\n container.style.height = \"1px\";\n container.style.overflow = \"hidden\";\n container.style.pointerEvents = \"none\";\n container.style.visibility = \"hidden\";\n container.style.zIndex = \"-1\";\n\n document.body.appendChild(container);\n return container;\n }\n\n /**\n * Sets up event handlers for the logout iframe\n */\n private setupLogoutIframeEventHandlers(): void {\n if (!this.iframeElement) return;\n\n this.iframeElement.onload = () => {\n this.logger.info(\"โ
Logout iframe loaded successfully\", {\n iframeSrc: this.iframeElement?.src,\n });\n\n // Try to detect redirect to our logout redirect URL\n this.checkLogoutIframeRedirect();\n };\n\n this.iframeElement.onerror = (event) => {\n this.logger.error(\"โ Logout iframe load error\", {\n event,\n iframeSrc: this.iframeElement?.src,\n });\n\n const error = new CivicAuthError(\n \"Logout iframe failed to load\",\n CivicAuthErrorCode.LOGOUT_FAILED,\n );\n\n this.onLogoutError(error);\n this.cleanupLogoutIframe();\n };\n }\n\n /**\n * Cleans up the logout iframe and container\n */\n public cleanupLogoutIframe(): void {\n this.logger.debug(\"๐งน Cleaning up logout iframe\");\n\n if (this.iframeManager) {\n // Get the container before cleanup to remove it from DOM\n const container = document.getElementById(\n \"civic-logout-iframe-container\",\n );\n\n this.iframeManager.cleanup();\n this.iframeManager = undefined;\n\n // Remove the hidden container from DOM\n if (container && container.parentNode) {\n container.parentNode.removeChild(container);\n }\n }\n\n if (this.iframeElement) {\n this.iframeElement = undefined;\n }\n\n this.logger.debug(\"โ
Logout iframe cleanup completed\");\n }\n\n /**\n * Get the current iframe element (for testing/debugging)\n */\n public getIframeElement(): HTMLIFrameElement | undefined {\n return this.iframeElement;\n }\n\n /**\n * Check if logout iframe is currently active\n */\n public isLogoutActive(): boolean {\n return !!(this.iframeManager && this.iframeElement);\n }\n\n /**\n * Checks if the logout iframe has redirected to our logout redirect URL\n */\n private checkLogoutIframeRedirect(): void {\n if (!this.config.logoutRedirectUrl) {\n this.logger.debug(\n \"No logoutRedirectUrl configured, treating initial load as completion\",\n );\n // If no logout redirect URL is configured, consider the initial load as completion\n this.onLogoutComplete();\n setTimeout(() => {\n this.cleanupLogoutIframe();\n }, 1000);\n return;\n }\n\n try {\n const currentIframeHref =\n this.iframeElement?.contentWindow?.location.href;\n\n if (currentIframeHref) {\n this.logger.debug(\"Logout iframe current href accessible\", {\n href: currentIframeHref,\n logoutRedirectUrl: this.config.logoutRedirectUrl,\n startsWithLogoutRedirect: currentIframeHref.startsWith(\n this.config.logoutRedirectUrl,\n ),\n });\n\n if (currentIframeHref.startsWith(this.config.logoutRedirectUrl)) {\n this.logger.info(\n \"โ
Logout iframe has navigated to logoutRedirectUrl - logout complete\",\n );\n this.onLogoutComplete();\n\n // Clean up the iframe after a short delay\n setTimeout(() => {\n this.cleanupLogoutIframe();\n }, 500);\n }\n }\n } catch (error) {\n this.logger.debug(\n \"Error checking logout iframe href (expected for cross-origin)\",\n {\n error: error instanceof Error ? error.message : String(error),\n iframeSrc: this.iframeElement?.src,\n logoutRedirectUrl: this.config.logoutRedirectUrl,\n },\n );\n // This is expected when the iframe is on the OIDC provider domain\n this.logger.debug(\n \"Logout iframe is on OIDC provider domain - using navigation monitoring\",\n {\n parentOrigin: window.location.origin,\n logoutRedirectUrl: this.config.logoutRedirectUrl,\n },\n );\n }\n }\n\n /**\n * Sets up navigation monitoring to detect when logout iframe redirects to logoutRedirectUrl\n * Similar to login's setupIframeNavigationMonitoring but for logout flow\n */\n private setupLogoutNavigationMonitoring(): void {\n if (!this.config.logoutRedirectUrl) {\n this.logger.debug(\n \"No logoutRedirectUrl configured, skipping navigation monitoring\",\n );\n return;\n }\n\n let monitoringInterval: number | undefined = undefined;\n let lastKnownUrl = \"\";\n\n const checkLogoutNavigation = () => {\n if (!this.iframeElement?.contentWindow) {\n if (monitoringInterval) {\n clearInterval(monitoringInterval);\n }\n return;\n }\n\n try {\n const currentUrl = this.iframeElement.contentWindow.location.href;\n\n if (currentUrl !== lastKnownUrl) {\n lastKnownUrl = currentUrl;\n this.logger.debug(\"Logout iframe navigation detected\", {\n newUrl: currentUrl,\n logoutRedirectUrl: this.config.logoutRedirectUrl,\n isLogoutRedirectUrl: currentUrl.startsWith(\n this.config.logoutRedirectUrl,\n ),\n });\n\n // Check if iframe has navigated to our logout redirect URL\n if (currentUrl.startsWith(this.config.logoutRedirectUrl)) {\n this.logger.info(\n \"โ
Logout iframe navigated to logoutRedirectUrl - logout complete\",\n );\n\n if (monitoringInterval) {\n clearInterval(monitoringInterval);\n }\n\n this.onLogoutComplete();\n\n // Clean up the iframe after a short delay\n setTimeout(() => {\n this.cleanupLogoutIframe();\n }, 500);\n }\n }\n } catch (error) {\n // Expected when iframe is on different origin\n // Only log if we haven't seen this before\n if (lastKnownUrl !== \"cross-origin\") {\n lastKnownUrl = \"cross-origin\";\n this.logger.debug(\n \"Logout iframe on cross-origin domain (expected during logout flow)\",\n { logoutRedirectUrl: this.config.logoutRedirectUrl },\n );\n }\n }\n };\n\n // Check immediately and then every 100ms for faster detection\n checkLogoutNavigation();\n monitoringInterval = window.setInterval(checkLogoutNavigation, 100);\n\n // Store cleanup function to clear monitoring\n const originalCleanup = this.cleanupLogoutIframe.bind(this);\n this.cleanupLogoutIframe = () => {\n if (monitoringInterval) {\n clearInterval(monitoringInterval);\n }\n originalCleanup();\n };\n }\n}\n"]}
|