@civic/auth 0.9.1-beta.2 โ†’ 0.9.1-beta.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/lib/oauth.d.ts +2 -1
  3. package/dist/lib/oauth.d.ts.map +1 -1
  4. package/dist/lib/oauth.js +1 -1
  5. package/dist/lib/oauth.js.map +1 -1
  6. package/dist/nextjs/config.d.ts +1 -0
  7. package/dist/nextjs/config.d.ts.map +1 -1
  8. package/dist/react-router-7/components/UserButton.d.ts.map +1 -1
  9. package/dist/react-router-7/components/UserButton.js +3 -1
  10. package/dist/react-router-7/components/UserButton.js.map +1 -1
  11. package/dist/react-router-7/cookies.d.ts +1 -1
  12. package/dist/react-router-7/cookies.d.ts.map +1 -1
  13. package/dist/react-router-7/cookies.js +3 -9
  14. package/dist/react-router-7/cookies.js.map +1 -1
  15. package/dist/react-router-7/useUser.d.ts +0 -2
  16. package/dist/react-router-7/useUser.d.ts.map +1 -1
  17. package/dist/react-router-7/useUser.js +1 -11
  18. package/dist/react-router-7/useUser.js.map +1 -1
  19. package/dist/server/config.d.ts +1 -0
  20. package/dist/server/config.d.ts.map +1 -1
  21. package/dist/server/config.js.map +1 -1
  22. package/dist/shared/version.d.ts +1 -1
  23. package/dist/shared/version.js +1 -1
  24. package/dist/shared/version.js.map +1 -1
  25. package/dist/vanillajs/auth/SessionManager.d.ts +9 -1
  26. package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
  27. package/dist/vanillajs/auth/SessionManager.js +103 -47
  28. package/dist/vanillajs/auth/SessionManager.js.map +1 -1
  29. package/package.json +4 -3
  30. package/dist/vanillajs/auth/handlers/LogoutHandler.d.ts +0 -57
  31. package/dist/vanillajs/auth/handlers/LogoutHandler.d.ts.map +0 -1
  32. package/dist/vanillajs/auth/handlers/LogoutHandler.js +0 -246
  33. 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"]}