@civic/auth 0.6.1-beta.3 → 0.6.1

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 (94) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +7 -0
  3. package/dist/nextjs/config.d.ts.map +1 -1
  4. package/dist/nextjs/config.js +1 -5
  5. package/dist/nextjs/config.js.map +1 -1
  6. package/dist/nextjs/hooks/useUserCookie.d.ts.map +1 -1
  7. package/dist/nextjs/hooks/useUserCookie.js.map +1 -1
  8. package/dist/nextjs/middleware.d.ts.map +1 -1
  9. package/dist/nextjs/middleware.js +51 -18
  10. package/dist/nextjs/middleware.js.map +1 -1
  11. package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
  12. package/dist/nextjs/providers/NextAuthProvider.js +0 -1
  13. package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
  14. package/dist/nextjs/routeHandler.d.ts.map +1 -1
  15. package/dist/nextjs/routeHandler.js +8 -0
  16. package/dist/nextjs/routeHandler.js.map +1 -1
  17. package/dist/shared/hooks/useSignIn.d.ts +4 -9
  18. package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
  19. package/dist/shared/hooks/useSignIn.js +42 -75
  20. package/dist/shared/hooks/useSignIn.js.map +1 -1
  21. package/dist/shared/providers/AuthContext.d.ts +2 -7
  22. package/dist/shared/providers/AuthContext.d.ts.map +1 -1
  23. package/dist/shared/providers/AuthContext.js.map +1 -1
  24. package/dist/shared/providers/UserProvider.d.ts +1 -5
  25. package/dist/shared/providers/UserProvider.d.ts.map +1 -1
  26. package/dist/shared/providers/UserProvider.js.map +1 -1
  27. package/dist/shared/version.d.ts +1 -1
  28. package/dist/shared/version.d.ts.map +1 -1
  29. package/dist/shared/version.js +1 -1
  30. package/dist/shared/version.js.map +1 -1
  31. package/dist/vanillajs/auth/AuthenticationEvents.d.ts.map +1 -1
  32. package/dist/vanillajs/auth/AuthenticationEvents.js +2 -2
  33. package/dist/vanillajs/auth/AuthenticationEvents.js.map +1 -1
  34. package/dist/vanillajs/auth/CivicAuth.d.ts +107 -68
  35. package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
  36. package/dist/vanillajs/auth/CivicAuth.js +412 -389
  37. package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
  38. package/dist/vanillajs/auth/{handlers/OAuthCallbackHandler.d.ts → OAuthCallbackHandler.d.ts} +2 -2
  39. package/dist/vanillajs/auth/OAuthCallbackHandler.d.ts.map +1 -0
  40. package/dist/vanillajs/auth/OAuthCallbackHandler.js +143 -0
  41. package/dist/vanillajs/auth/OAuthCallbackHandler.js.map +1 -0
  42. package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
  43. package/dist/vanillajs/auth/SessionManager.js +2 -2
  44. package/dist/vanillajs/auth/SessionManager.js.map +1 -1
  45. package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -1
  46. package/dist/vanillajs/auth/TokenRefresher.js +2 -2
  47. package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
  48. package/dist/vanillajs/iframe/IframeManager.d.ts +0 -33
  49. package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
  50. package/dist/vanillajs/iframe/IframeManager.js +36 -163
  51. package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
  52. package/dist/vanillajs/index.d.ts +2 -2
  53. package/dist/vanillajs/index.d.ts.map +1 -1
  54. package/dist/vanillajs/index.js +2 -2
  55. package/dist/vanillajs/index.js.map +1 -1
  56. package/dist/vanillajs/services/ApiService.d.ts.map +1 -1
  57. package/dist/vanillajs/services/ApiService.js +2 -2
  58. package/dist/vanillajs/services/ApiService.js.map +1 -1
  59. package/dist/vanillajs/types/index.d.ts +10 -15
  60. package/dist/vanillajs/types/index.d.ts.map +1 -1
  61. package/dist/vanillajs/types/index.js +10 -15
  62. package/dist/vanillajs/types/index.js.map +1 -1
  63. package/dist/vanillajs/utils/auth-utils.d.ts +1 -2
  64. package/dist/vanillajs/utils/auth-utils.d.ts.map +1 -1
  65. package/dist/vanillajs/utils/auth-utils.js +3 -6
  66. package/dist/vanillajs/utils/auth-utils.js.map +1 -1
  67. package/dist/vanillajs/utils/logger.d.ts +15 -16
  68. package/dist/vanillajs/utils/logger.d.ts.map +1 -1
  69. package/dist/vanillajs/utils/logger.js +19 -35
  70. package/dist/vanillajs/utils/logger.js.map +1 -1
  71. package/package.json +1 -6
  72. package/dist/vanillajs/auth/config/ConfigProcessor.d.ts +0 -6
  73. package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +0 -1
  74. package/dist/vanillajs/auth/config/ConfigProcessor.js +0 -59
  75. package/dist/vanillajs/auth/config/ConfigProcessor.js.map +0 -1
  76. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +0 -40
  77. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +0 -1
  78. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +0 -388
  79. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +0 -1
  80. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +0 -170
  81. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +0 -1
  82. package/dist/vanillajs/auth/handlers/MessageHandler.js +0 -367
  83. package/dist/vanillajs/auth/handlers/MessageHandler.js.map +0 -1
  84. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.d.ts.map +0 -1
  85. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js +0 -301
  86. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js.map +0 -1
  87. package/dist/vanillajs/auth/handlers/PopupHandler.d.ts +0 -108
  88. package/dist/vanillajs/auth/handlers/PopupHandler.d.ts.map +0 -1
  89. package/dist/vanillajs/auth/handlers/PopupHandler.js +0 -333
  90. package/dist/vanillajs/auth/handlers/PopupHandler.js.map +0 -1
  91. package/dist/vanillajs/auth/types/AuthTypes.d.ts +0 -128
  92. package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +0 -1
  93. package/dist/vanillajs/auth/types/AuthTypes.js +0 -40
  94. package/dist/vanillajs/auth/types/AuthTypes.js.map +0 -1
@@ -1,55 +1,105 @@
1
1
  import { AuthEvent } from "../types/index.js";
2
+ import { LocalStorageAdapter } from "../../browser/storage.js";
3
+ import { handleOAuthRedirectPage } from "./OAuthCallbackHandler.js";
2
4
  import { buildAuthUrl } from "../utils/auth-utils.js";
3
- import { createMainLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
5
+ import { createLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
6
+ import { SignalObserver } from "../iframe/SignalObserver.js";
4
7
  import { GenericPublicClientPKCEProducer } from "../../services/PKCE.js";
5
8
  import { generateState } from "../../lib/oauth.js";
6
9
  import { SessionManager } from "./SessionManager.js";
7
- import { PopupError } from "../../services/types.js";
8
- import { handleOAuthRedirectPage } from "./handlers/OAuthCallbackHandler.js";
9
- import { generateOauthLogoutUrl, clearTokens, retrieveTokens, } from "../../shared/lib/util.js";
10
- import { CivicAuthError, CivicAuthErrorCode, CIVIC_AUTH_CONSTANTS, } from "./types/AuthTypes.js";
11
- import { processConfigWithDefaults } from "./config/ConfigProcessor.js";
12
- import { MessageHandler } from "./handlers/MessageHandler.js";
13
- import { PopupHandler } from "./handlers/PopupHandler.js";
14
- import { IframeAuthHandler } from "./handlers/IframeAuthHandler.js";
10
+ import { IframeManager } from "../iframe/IframeManager.js";
11
+ /**
12
+ * Error codes for CivicAuth errors
13
+ */
14
+ export var CivicAuthErrorCode;
15
+ (function (CivicAuthErrorCode) {
16
+ CivicAuthErrorCode["CONFIG_REQUIRED"] = "CONFIG_REQUIRED";
17
+ CivicAuthErrorCode["INIT_FAILED"] = "INIT_FAILED";
18
+ CivicAuthErrorCode["ENDPOINTS_NOT_INITIALIZED"] = "ENDPOINTS_NOT_INITIALIZED";
19
+ CivicAuthErrorCode["CONTAINER_NOT_FOUND"] = "CONTAINER_NOT_FOUND";
20
+ CivicAuthErrorCode["AUTH_PROCESS_TIMEOUT"] = "AUTH_PROCESS_TIMEOUT";
21
+ CivicAuthErrorCode["IFRAME_LOAD_ERROR"] = "IFRAME_LOAD_ERROR";
22
+ CivicAuthErrorCode["INVALID_MESSAGE"] = "INVALID_MESSAGE";
23
+ })(CivicAuthErrorCode || (CivicAuthErrorCode = {}));
24
+ /**
25
+ * Constants for the auth client
26
+ */
27
+ const CONSTANTS = {
28
+ DEFAULT_IFRAME_ID: "civic-auth-iframe",
29
+ DEFAULT_AUTH_PROCESS_TIMEOUT: 60000, // 60 seconds
30
+ SUCCESS_SIGNAL_ID: "civic-auth-success-signal",
31
+ ERROR_SIGNAL_ID: "civic-auth-error-signal",
32
+ };
33
+ class CivicAuthError extends Error {
34
+ code;
35
+ constructor(message, code) {
36
+ super(message);
37
+ this.code = code;
38
+ this.name = "CivicAuthError";
39
+ }
40
+ }
41
+ /**
42
+ * Process the configuration with defaults
43
+ */
44
+ function processConfigWithDefaults(config) {
45
+ const loggingConfig = {
46
+ enabled: true,
47
+ namespace: "iframe",
48
+ level: "debug",
49
+ ...config.logging,
50
+ };
51
+ return {
52
+ ...config,
53
+ displayMode: config.displayMode || "iframe",
54
+ authProcessTimeout: config.authProcessTimeout || CONSTANTS.DEFAULT_AUTH_PROCESS_TIMEOUT,
55
+ iframeId: config.iframeId || CONSTANTS.DEFAULT_IFRAME_ID,
56
+ logging: loggingConfig,
57
+ storageAdapter: config.storageAdapter || new LocalStorageAdapter(),
58
+ };
59
+ }
15
60
  /**
16
61
  * CivicAuth client for handling OAuth authentication
17
- *
18
- * This is a refactored version that uses a modular architecture for better maintainability.
19
62
  */
20
63
  export class CivicAuth {
64
+ /**
65
+ * Internal configuration with all optional properties resolved to required ones.
66
+ *
67
+ * We extend CivicAuthClientConfig rather than making these properties required
68
+ * in the public interface to maintain better DX (optional properties) while
69
+ * ensuring type safety internally after defaults are applied.
70
+ */
21
71
  config;
22
- storage;
23
- endpoints;
24
- logger;
25
- sessionManager;
26
- initialDisplayMode;
27
- // Authentication state
72
+ iframeElement;
73
+ observer;
28
74
  authPromise;
29
75
  authPromiseResolve;
30
76
  authPromiseReject;
31
77
  authProcessTimeoutHandle;
32
- popupFailureTimeoutHandle;
33
- hasPopupFailed = false;
34
- // Handlers
35
- messageHandler;
36
- popupHandler;
37
- iframeAuthHandler;
78
+ messageEventHandler;
79
+ storage;
80
+ endpoints;
81
+ logger;
82
+ sessionManager;
83
+ iframeManager;
38
84
  /**
39
- * Private constructor - initializes configuration and handlers.
85
+ * Private constructor for CivicAuth.
40
86
  * Use {@link CivicAuth.create} to create a new instance.
87
+ * @param config - Configuration options for the auth client
88
+ * @throws {CivicAuthError} If required configuration is missing
89
+ * @private
41
90
  */
42
91
  constructor(config) {
43
- // Process config with defaults and validation
92
+ // Process config with defaults
44
93
  this.config = processConfigWithDefaults(config);
45
- this.initialDisplayMode = this.config.displayMode;
46
- // Configure logging
94
+ // Configure logging based on config
47
95
  configureLogging(this.config.logging);
48
- // Initialize logger - always use "vanillajs" as base namespace
96
+ // Initialize logger based on config
49
97
  if (this.config.logging?.enabled) {
50
- this.logger = createMainLogger("vanillajs"); // Always use "vanillajs"
98
+ const namespace = this.config.logging.namespace || "iframe";
99
+ this.logger = createLogger(namespace);
51
100
  }
52
101
  else {
102
+ // Create a no-op logger when logging is disabled
53
103
  this.logger = {
54
104
  debug: () => { },
55
105
  info: () => { },
@@ -57,14 +107,26 @@ export class CivicAuth {
57
107
  error: () => { },
58
108
  };
59
109
  }
110
+ // Set this logger as the current logger
60
111
  setCurrentLogger(this.logger);
112
+ // Use the storage adapter from processed config (guaranteed to be present)
61
113
  this.storage = this.config.storageAdapter;
62
114
  // Initialize SessionManager if events are provided
63
115
  if (config.events) {
64
116
  this.sessionManager = new SessionManager(this.storage, config.events);
65
117
  }
66
- // Initialize handlers
67
- this.initializeHandlers();
118
+ // Validate required configuration
119
+ const requiredConfigs = [
120
+ { key: "clientId", value: config.clientId },
121
+ { key: "targetContainerElement", value: config.targetContainerElement },
122
+ { key: "textSignals.success", value: config.textSignals?.success },
123
+ ];
124
+ for (const { key, value } of requiredConfigs) {
125
+ if (!value) {
126
+ throw new CivicAuthError(`CivicAuth: ${key} is required.`, CivicAuthErrorCode.CONFIG_REQUIRED);
127
+ }
128
+ }
129
+ this.messageEventHandler = this.handleIframeMessage.bind(this);
68
130
  }
69
131
  /**
70
132
  * Creates and initializes a new instance of CivicAuth.
@@ -78,12 +140,9 @@ export class CivicAuth {
78
140
  * ```typescript
79
141
  * const auth = await CivicAuth.create({
80
142
  * clientId: "your-client-id",
81
- * // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
82
- * redirectUrl: "https://your-app.com/callback", // optional
83
- * // oauthServerBaseUrl is optional - defaults to "https://auth.civic.com/oauth/"
84
- * oauthServerBaseUrl: "https://auth-server.com/", // optional
85
- * // scopes is optional - defaults to ['openid', 'profile', 'email', 'offline_access']
86
- * scopes: ["openid", "profile"], // optional
143
+ * redirectUrl: "https://your-app.com/callback",
144
+ * oauthServerBaseUrl: "https://auth-server.com/",
145
+ * scopes: ["openid", "profile"],
87
146
  * targetContainerElement: "auth-container",
88
147
  * textSignals: {
89
148
  * success: "Authentication successful!"
@@ -98,16 +157,11 @@ export class CivicAuth {
98
157
  }
99
158
  /**
100
159
  * Initializes the auth client and checks for callback handling
160
+ * @throws {CivicAuthError} If initialization fails
101
161
  */
102
162
  async init() {
103
- this.logger.info("🚀 Initializing CivicAuth", {
104
- currentUrl: window.location.href,
105
- redirectUrl: this.config.redirectUrl,
106
- oauthServerBaseUrl: this.config.oauthServerBaseUrl,
107
- isCallbackUrl: window.location.href.startsWith(this.config.redirectUrl),
108
- });
109
163
  try {
110
- // Get OAuth endpoints
164
+ // Get OAuth endpoints from well-known configuration
111
165
  this.endpoints = {
112
166
  auth: `${this.config.oauthServerBaseUrl}auth`,
113
167
  token: `${this.config.oauthServerBaseUrl}token`,
@@ -115,10 +169,7 @@ export class CivicAuth {
115
169
  userinfo: `${this.config.oauthServerBaseUrl}userinfo`,
116
170
  endsession: `${this.config.oauthServerBaseUrl}endsession`,
117
171
  };
118
- this.logger.info("🔗 OAuth endpoints configured", {
119
- endpoints: this.endpoints,
120
- });
121
- // Initialize SessionManager with auth config
172
+ // Initialize SessionManager with auth config for token refresh
122
173
  if (this.sessionManager) {
123
174
  const authConfig = {
124
175
  clientId: this.config.clientId,
@@ -127,32 +178,18 @@ export class CivicAuth {
127
178
  scopes: this.config.scopes,
128
179
  endpoints: this.endpoints,
129
180
  };
130
- this.logger.info("🔧 Initializing SessionManager", { authConfig });
131
181
  await this.sessionManager.initializeWithAuthConfig(authConfig);
132
182
  }
133
183
  // Check if we're on the callback page
134
- const isCallbackPage = window.location.href.startsWith(this.config.redirectUrl);
135
- this.logger.info("🔍 Callback page check", {
136
- isCallbackPage,
137
- currentUrl: window.location.href,
138
- redirectUrl: this.config.redirectUrl,
139
- });
140
- if (isCallbackPage) {
141
- this.logger.info("📞 Processing callback page");
184
+ if (window.location.href.startsWith(this.config.redirectUrl)) {
142
185
  await this.handleCallback();
143
186
  }
144
- else {
145
- this.logger.info("🏠 Not a callback page, initialization complete");
146
- }
147
187
  }
148
188
  catch (error) {
149
189
  const errorMessage = error instanceof Error
150
190
  ? error.message
151
191
  : "Failed to initialize authentication";
152
- this.logger.error(" CivicAuth initialization failed", {
153
- error: errorMessage,
154
- stack: error instanceof Error ? error.stack : undefined,
155
- });
192
+ this.logger.error("Failed to initialize CivicAuth:", { error });
156
193
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
157
194
  detail: errorMessage,
158
195
  });
@@ -160,33 +197,82 @@ export class CivicAuth {
160
197
  }
161
198
  }
162
199
  /**
163
- * Initialize all handlers with proper configuration
200
+ * Gets the container element for the auth iframe
201
+ * @returns The container element or null if not found
164
202
  */
165
- initializeHandlers() {
166
- const handlerConfig = {
167
- config: this.config,
168
- logger: this.logger,
169
- onAuthSuccess: this.handleAuthSuccess.bind(this),
170
- onAuthError: this.handleAuthError.bind(this),
171
- cleanup: this.cleanup.bind(this),
172
- };
173
- this.messageHandler = new MessageHandler({
174
- ...handlerConfig,
175
- onPopupFailure: this.handlePopupFailure.bind(this),
176
- });
177
- this.popupHandler = new PopupHandler(handlerConfig);
178
- this.iframeAuthHandler = new IframeAuthHandler({
179
- ...handlerConfig,
180
- messageHandler: this.messageHandler.handleMessage,
181
- });
203
+ getContainerElement() {
204
+ if (typeof this.config.targetContainerElement === "string") {
205
+ const element = document.getElementById(this.config.targetContainerElement);
206
+ if (!element) {
207
+ this.logger.warn(`Container element with ID "${this.config.targetContainerElement}" not found`);
208
+ }
209
+ return element;
210
+ }
211
+ return this.config.targetContainerElement;
212
+ }
213
+ /**
214
+ * Determines the appropriate iframe display mode based on container characteristics
215
+ * @param container The HTML element that will contain the iframe
216
+ * @returns "modal" for full-screen overlay or "embedded" for in-container display
217
+ */
218
+ determineIframeDisplayMode(container) {
219
+ // Use explicit config if provided
220
+ if (this.config.iframeDisplayMode) {
221
+ this.logger.debug(`Using configured iframe display mode: ${this.config.iframeDisplayMode}`);
222
+ return this.config.iframeDisplayMode;
223
+ }
224
+ // Analyze container characteristics to determine best display mode
225
+ const containerRect = container.getBoundingClientRect();
226
+ const containerStyles = window.getComputedStyle(container);
227
+ // Get container dimensions
228
+ const containerWidth = containerRect.width;
229
+ const containerHeight = containerRect.height;
230
+ // Check if container is positioned in a way that suggests it's meant for modal display
231
+ const isFullScreenContainer = containerStyles.position === "fixed" &&
232
+ (containerWidth >= window.innerWidth * 0.9 ||
233
+ containerHeight >= window.innerHeight * 0.9);
234
+ // Modal mode thresholds - if container is too small, use modal for better UX
235
+ const MIN_EMBEDDED_WIDTH = 320; // Same as modal content wrapper width
236
+ const MIN_EMBEDDED_HEIGHT = 400; // Reasonable minimum for embedded auth flow
237
+ // Determine mode based on container characteristics
238
+ if (isFullScreenContainer) {
239
+ this.logger.debug("Container appears to be full-screen positioned, using modal mode", { containerWidth, containerHeight, position: containerStyles.position });
240
+ return "modal";
241
+ }
242
+ if (containerWidth < MIN_EMBEDDED_WIDTH ||
243
+ containerHeight < MIN_EMBEDDED_HEIGHT) {
244
+ this.logger.debug(`Container too small for embedded mode (${containerWidth}x${containerHeight}), using modal mode`, {
245
+ containerWidth,
246
+ containerHeight,
247
+ MIN_EMBEDDED_WIDTH,
248
+ MIN_EMBEDDED_HEIGHT,
249
+ });
250
+ return "modal";
251
+ }
252
+ // Check if container has sufficient space and is properly positioned for embedding
253
+ const hasAdequateSpace = containerWidth >= MIN_EMBEDDED_WIDTH &&
254
+ containerHeight >= MIN_EMBEDDED_HEIGHT;
255
+ const isEmbeddablePosition = containerStyles.position === "relative" ||
256
+ containerStyles.position === "static" ||
257
+ containerStyles.position === "";
258
+ if (hasAdequateSpace && isEmbeddablePosition) {
259
+ this.logger.debug("Container has adequate space and positioning for embedded mode", { containerWidth, containerHeight, position: containerStyles.position });
260
+ return "embedded";
261
+ }
262
+ // Default to modal mode if container characteristics are unclear
263
+ this.logger.debug("Container characteristics unclear, defaulting to modal mode for better UX", { containerWidth, containerHeight, position: containerStyles.position });
264
+ return "modal";
182
265
  }
183
266
  /**
184
267
  * Builds the authentication URL with PKCE challenge
268
+ * @returns The complete authentication URL
269
+ * @throws {CivicAuthError} If endpoints are not initialized
185
270
  */
186
271
  async buildAuthUrl() {
187
272
  if (!this.endpoints) {
188
273
  throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
189
274
  }
275
+ // Use storage directly since it's now AuthStorage
190
276
  const pkceProducer = new GenericPublicClientPKCEProducer(this.storage);
191
277
  const codeChallenge = await pkceProducer.getCodeChallenge();
192
278
  const state = this.config.initialState ||
@@ -201,265 +287,293 @@ export class CivicAuth {
201
287
  codeChallenge,
202
288
  state,
203
289
  prompt: this.config.prompt,
204
- nonce: this.config.nonce,
205
290
  });
206
291
  }
292
+ handleIframeMessage(event) {
293
+ const expectedOrigin = new URL(this.config.oauthServerBaseUrl).origin;
294
+ this.logIncomingMessage(event, expectedOrigin);
295
+ if (!this.isValidMessageSource(event, expectedOrigin)) {
296
+ return;
297
+ }
298
+ this.handleValidMessage(event);
299
+ }
300
+ logIncomingMessage(event, expectedOrigin) {
301
+ this.logger.debug("Global window received message:", {
302
+ data: event.data,
303
+ origin: event.origin,
304
+ sourceProvided: !!event.source,
305
+ iframeContentWindow: this.iframeElement?.contentWindow,
306
+ expectedIframeOrigin: expectedOrigin,
307
+ });
308
+ }
309
+ isValidMessageSource(event, expectedOrigin) {
310
+ const isValidOrigin = event.origin === expectedOrigin;
311
+ const isValidSource = event.source === this.iframeElement?.contentWindow;
312
+ if (!isValidOrigin) {
313
+ this.logger.warn("Ignored message from unexpected origin.", {
314
+ receivedOrigin: event.origin,
315
+ expectedOrigin,
316
+ iframeSrc: this.iframeElement?.src,
317
+ });
318
+ }
319
+ if (!isValidSource) {
320
+ this.logger.warn("Ignored message from unexpected source.", {
321
+ isSourceProvided: !!event.source,
322
+ isIframeContentWindowAvailable: !!this.iframeElement?.contentWindow,
323
+ iframeSrc: this.iframeElement?.src,
324
+ });
325
+ }
326
+ return isValidOrigin && isValidSource;
327
+ }
328
+ handleValidMessage(event) {
329
+ this.logger.info("Message from configured iframe source and origin received", {
330
+ data: event.data,
331
+ iframeSrc: this.iframeElement?.src,
332
+ });
333
+ const message = event.data;
334
+ const messageType = message?.type;
335
+ switch (messageType) {
336
+ case "auth_success":
337
+ this.handleAuthSuccess(message);
338
+ break;
339
+ case "auth_error":
340
+ this.handleAuthError(message);
341
+ break;
342
+ default:
343
+ this.logger.debug("Message from iframe did not match expected types (auth_success, auth_error)", { data: event.data });
344
+ }
345
+ }
346
+ handleAuthSuccess(data) {
347
+ this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
348
+ detail: "Success signal received via postMessage",
349
+ data,
350
+ });
351
+ this.authPromiseResolve?.(data?.data || {});
352
+ this.cleanup();
353
+ }
354
+ handleAuthError(data) {
355
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
356
+ detail: "Error signal received via postMessage",
357
+ error: data,
358
+ });
359
+ this.authPromiseReject?.(new CivicAuthError(data?.detail || "Error signal received via postMessage", CivicAuthErrorCode.INVALID_MESSAGE));
360
+ this.cleanup();
361
+ }
362
+ setupSignalObserver(iframeDoc) {
363
+ const signalObserver = new SignalObserver({
364
+ textSignals: this.config.textSignals,
365
+ events: this.config.events,
366
+ logger: this.logger,
367
+ }, this.authPromiseResolve, this.authPromiseReject, () => this.cleanup());
368
+ signalObserver.setup(iframeDoc);
369
+ }
370
+ async handleNewTabAuth(fullAuthUrl, reject) {
371
+ const popupWindow = window.open(fullAuthUrl, "_blank");
372
+ if (!popupWindow) {
373
+ const error = new CivicAuthError("Failed to open popup window. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
374
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
375
+ detail: error.message,
376
+ });
377
+ reject(error);
378
+ }
379
+ }
380
+ async handleIframeAuth(fullAuthUrl, reject) {
381
+ const container = this.getContainerElement();
382
+ if (!container) {
383
+ const error = new CivicAuthError("Target container element not found.", CivicAuthErrorCode.CONTAINER_NOT_FOUND);
384
+ this.logger.error(error.message);
385
+ reject(error);
386
+ return;
387
+ }
388
+ this.logger.debug("Creating iframe with modal backdrop", {
389
+ url: fullAuthUrl,
390
+ containerId: container?.id,
391
+ iframeId: this.config.iframeId,
392
+ origin: window.location.origin,
393
+ });
394
+ // Determine the actual display mode for IframeManager
395
+ // For iframe displayMode, we need to decide between modal and embedded based on container
396
+ const iframeDisplayMode = this.determineIframeDisplayMode(container);
397
+ this.logger.debug(`🎯 CivicAuth: Creating IframeManager with display mode: ${iframeDisplayMode}`);
398
+ // Create IframeManager with appropriate display mode
399
+ this.iframeManager = new IframeManager({
400
+ container: container,
401
+ displayMode: iframeDisplayMode,
402
+ iframeId: this.config.iframeId,
403
+ onClose: () => {
404
+ this.logger.debug("Authentication close requested by user (backdrop click, close button, or Escape key)");
405
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
406
+ detail: "Authentication cancelled by user",
407
+ });
408
+ reject(new CivicAuthError("Authentication cancelled by user", CivicAuthErrorCode.AUTH_PROCESS_TIMEOUT));
409
+ this.cleanup();
410
+ },
411
+ });
412
+ // Create the iframe using IframeManager
413
+ this.iframeElement = this.iframeManager.createIframe(fullAuthUrl);
414
+ this.config.events?.emit(AuthEvent.SIGN_IN_STARTED, {
415
+ detail: "Iframe created with modal backdrop",
416
+ });
417
+ this.iframeElement.onload = () => {
418
+ this.logger.info("Iframe loaded", {
419
+ iframeSrc: this.iframeElement?.src,
420
+ currentOrigin: window.location.origin,
421
+ expectedAuthServerOrigin: new URL(this.config.oauthServerBaseUrl)
422
+ .origin,
423
+ });
424
+ if (!this.iframeElement?.contentWindow) {
425
+ const errorMsg = "Iframe content window not available after load.";
426
+ this.logger.error(errorMsg, {
427
+ iframeSrc: this.iframeElement?.src,
428
+ });
429
+ reject(new Error(errorMsg));
430
+ this.cleanup();
431
+ return;
432
+ }
433
+ // Set up postMessage listener for cross-origin communication
434
+ if (this.messageEventHandler) {
435
+ window.addEventListener("message", this.messageEventHandler);
436
+ this.logger.info("Added cross-origin message event listener for auth server communication", {
437
+ parentOrigin: window.location.origin,
438
+ authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
439
+ });
440
+ }
441
+ else {
442
+ this.logger.error("messageEventHandler was not defined when trying to add listener.");
443
+ }
444
+ // Try to detect redirect to our domain
445
+ try {
446
+ const currentIframeHref = this.iframeElement.contentWindow.location.href;
447
+ if (currentIframeHref.startsWith(this.config.redirectUrl)) {
448
+ this.logger.info("Iframe has navigated to redirectUrl (same-origin). Setting up DOM observer.");
449
+ if (this.iframeElement.contentDocument &&
450
+ this.iframeElement.contentDocument.body) {
451
+ this.setupSignalObserver(this.iframeElement.contentDocument);
452
+ }
453
+ }
454
+ }
455
+ catch (error) {
456
+ this.logger.error("Error checking iframe href", {
457
+ error,
458
+ iframeSrc: this.iframeElement?.src,
459
+ });
460
+ // This is expected when the iframe is on the auth server domain
461
+ this.logger.info("Iframe is on auth server domain - using postMessage for communication", {
462
+ parentOrigin: window.location.origin,
463
+ authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
464
+ });
465
+ }
466
+ };
467
+ this.iframeElement.onerror = (event) => {
468
+ this.logger.error("Iframe load error", {
469
+ event,
470
+ iframeSrc: this.iframeElement?.src,
471
+ currentOrigin: window.location.origin,
472
+ });
473
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
474
+ detail: "Iframe load error",
475
+ error: event,
476
+ });
477
+ reject(new Error("Iframe failed to load."));
478
+ this.cleanup();
479
+ };
480
+ }
207
481
  /**
208
482
  * Starts the authentication process
209
483
  * @returns A promise that resolves with the authentication result
210
484
  * @throws {CivicAuthError} If authentication fails or times out
211
485
  */
212
486
  async startAuthentication() {
213
- this.logger.info("🎬 Starting authentication process", {
214
- displayMode: this.config.displayMode,
215
- userAgent: navigator.userAgent,
216
- currentUrl: window.location.href,
217
- });
218
487
  if (!this.endpoints) {
219
488
  const error = new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
220
- this.logger.error("❌ Endpoints not initialized", {
221
- error: error.message,
222
- });
223
489
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
224
490
  detail: error.message,
225
491
  });
226
492
  throw error;
227
493
  }
228
494
  if (this.authPromise) {
229
- this.logger.info("Authentication already in progress, returning existing promise");
495
+ this.logger.info("Authentication already in progress, returning existing promise");
230
496
  return this.authPromise;
231
497
  }
232
498
  const fullAuthUrl = await this.buildAuthUrl();
233
- this.logger.info("🔗 Built authentication URL", {
234
- url: fullAuthUrl,
499
+ this.logger.info("Starting authentication process", {
500
+ constructedIframeUrl: fullAuthUrl,
235
501
  displayMode: this.config.displayMode,
236
502
  authProcessTimeout: this.config.authProcessTimeout,
237
503
  });
238
504
  this.authPromise = new Promise((resolve, reject) => {
239
505
  this.authPromiseResolve = resolve;
240
506
  this.authPromiseReject = reject;
241
- this.handleAuthenticationByDisplayMode(fullAuthUrl);
242
- this.setupAuthenticationTimeout();
243
- });
244
- return this.authPromise;
245
- }
246
- /**
247
- * Handle authentication based on display mode
248
- */
249
- async handleAuthenticationByDisplayMode(fullAuthUrl) {
250
- this.logger.info("🎯 Handling authentication with display mode", {
251
- displayMode: this.config.displayMode,
252
- });
253
- try {
507
+ // Handle different display modes
254
508
  switch (this.config.displayMode) {
255
509
  case "redirect":
256
- this.logger.info("🌐 Using redirect mode");
510
+ // For redirect mode, just navigate to the auth URL
257
511
  window.location.href = fullAuthUrl;
258
512
  break;
259
513
  case "new_tab":
260
- this.logger.info("📱 Using new_tab mode");
261
- if (!this.popupHandler) {
262
- throw new Error("Popup handler not initialized");
263
- }
264
- await this.popupHandler.handleNewTabAuth(fullAuthUrl);
514
+ this.handleNewTabAuth(fullAuthUrl, reject);
265
515
  break;
266
516
  case "iframe":
267
- default: {
268
- this.logger.info("🖼️ Using iframe mode");
269
- if (!this.iframeAuthHandler || !this.messageHandler) {
270
- throw new Error("Iframe handler not initialized");
271
- }
272
- const iframeElement = await this.iframeAuthHandler.handleIframeAuth(fullAuthUrl);
273
- this.messageHandler.updateIframeElement(iframeElement);
517
+ default:
518
+ this.handleIframeAuth(fullAuthUrl, reject);
274
519
  break;
275
- }
276
- }
277
- }
278
- catch (error) {
279
- if (error instanceof PopupError) {
280
- await this.handlePopupErrorWithFallback(fullAuthUrl, error);
281
- }
282
- else {
283
- this.handleAuthError(error instanceof Error ? error : new Error(String(error)));
284
520
  }
285
- }
286
- }
287
- /**
288
- * Handle popup error with redirect fallback
289
- */
290
- async handlePopupErrorWithFallback(fullAuthUrl, error) {
291
- this.logger.warn("🚫 Popup failed, falling back to redirect mode", {
292
- originalDisplayMode: this.config.displayMode,
293
- error: error.message,
294
- });
295
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
296
- detail: "Popup blocked, falling back to redirect mode",
297
- });
298
- try {
299
- this.logger.info("🔄 Attempting redirect fallback");
300
- // Clean up current authentication attempt
301
- this.cleanup();
302
- // Always switch to redirect mode for Safari compatibility
303
- this.config.displayMode = "redirect";
304
- // Regenerate the auth URL with updated display mode in state
305
- const fallbackAuthUrl = await this.buildAuthUrl();
306
- this.logger.info("🌐 Redirecting to auth URL", { url: fallbackAuthUrl });
307
- window.location.href = fallbackAuthUrl;
308
- this.logger.info("✅ Redirect initiated successfully");
309
- }
310
- catch (redirectError) {
311
- this.logger.error("❌ Redirect fallback failed", {
312
- error: redirectError,
313
- redirectUrl: fullAuthUrl,
314
- });
315
- const fallbackError = new CivicAuthError("Failed to open popup window and redirect fallback failed. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
316
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
317
- detail: fallbackError.message,
318
- });
319
- this.handleAuthError(fallbackError);
320
- }
321
- }
322
- /**
323
- * Setup authentication timeout
324
- */
325
- setupAuthenticationTimeout() {
326
- if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
327
- this.logger.debug("⏰ Setting up authentication timeout", {
328
- authProcessTimeout: this.config.authProcessTimeout,
329
- displayMode: this.config.displayMode,
330
- });
331
- this.authProcessTimeoutHandle = window.setTimeout(() => {
332
- this.logger.error("⏰ Authentication timed out", {
333
- displayMode: this.config.displayMode,
334
- currentOrigin: window.location.origin,
521
+ // Set up timeout for all display modes
522
+ if (this.config.authProcessTimeout &&
523
+ this.config.authProcessTimeout > 0) {
524
+ this.logger.debug("Setting up authentication timeout", {
335
525
  authProcessTimeout: this.config.authProcessTimeout,
526
+ displayMode: this.config.displayMode,
336
527
  });
337
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
338
- detail: "Authentication timed out",
339
- });
340
- const error = new CivicAuthError("Authentication timed out", CivicAuthErrorCode.AUTH_PROCESS_TIMEOUT);
341
- this.handleAuthError(error);
342
- }, this.config.authProcessTimeout);
343
- }
344
- }
345
- /**
346
- * Handle successful authentication
347
- */
348
- handleAuthSuccess(result) {
349
- this.logger.info("✅ Authentication successful");
350
- this.authPromiseResolve?.(result);
351
- this.cleanup();
352
- }
353
- /**
354
- * Handle authentication error
355
- */
356
- handleAuthError(error) {
357
- this.logger.error("❌ Authentication failed", { error: error.message });
358
- this.authPromiseReject?.(error);
359
- this.cleanup();
360
- }
361
- /**
362
- * Handle popup failure - simplified like React implementation
363
- */
364
- handlePopupFailure(failedUrl) {
365
- this.hasPopupFailed = true;
366
- this.logger.warn("Popup failed, using redirect mode instead...", {
367
- failedUrl,
528
+ this.authProcessTimeoutHandle = window.setTimeout(() => {
529
+ this.logger.warn("Authentication timed out", {
530
+ displayMode: this.config.displayMode,
531
+ currentOrigin: window.location.origin,
532
+ });
533
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
534
+ detail: "Authentication timed out",
535
+ });
536
+ reject(new Error("Authentication timed out."));
537
+ this.cleanup();
538
+ }, this.config.authProcessTimeout);
539
+ }
368
540
  });
369
- // Clean up iframe if it exists
370
- if (this.iframeAuthHandler) {
371
- this.iframeAuthHandler.cleanupIframe();
372
- }
373
- // Always redirect to the failed URL or build a new one
374
- if (failedUrl) {
375
- window.location.href = failedUrl;
376
- }
377
- else {
378
- this.buildAuthUrl()
379
- .then((authUrl) => {
380
- window.location.href = authUrl;
381
- })
382
- .catch((error) => {
383
- this.logger.error("Failed to build auth URL for redirect fallback", {
384
- error,
385
- });
386
- const fallbackError = new CivicAuthError("Failed to redirect for authentication", CivicAuthErrorCode.INIT_FAILED);
387
- this.handleAuthError(fallbackError);
388
- });
389
- }
541
+ return this.authPromise;
390
542
  }
391
543
  /**
392
- * Show popup failure message to user
544
+ * Cleans up resources and event listeners
393
545
  */
394
- showPopupFailureMessage(customMessage = "Authentication will continue in this window. Please wait...") {
395
- const container = this.getContainerElement();
396
- if (!container) {
397
- this.logger.warn("Cannot show popup failure message - container not found");
398
- return;
546
+ cleanup() {
547
+ this.logger.info("Cleaning up iframe authentication client");
548
+ if (this.observer) {
549
+ this.logger.debug("Disconnecting mutation observer");
550
+ this.observer.disconnect();
551
+ this.observer = undefined;
399
552
  }
400
- const existingMessage = document.getElementById("civic-auth-popup-failure-message");
401
- if (existingMessage && existingMessage.parentNode) {
402
- existingMessage.parentNode.removeChild(existingMessage);
553
+ if (this.messageEventHandler) {
554
+ window.removeEventListener("message", this.messageEventHandler);
555
+ this.logger.debug("Removed 'message' event listener from window.");
403
556
  }
404
- const messageOverlay = document.createElement("div");
405
- messageOverlay.id = "civic-auth-popup-failure-message";
406
- messageOverlay.style.cssText =
407
- "position:absolute;top:0;left:0;right:0;background:rgba(255,249,196,.95);border:1px solid #f59e0b;border-radius:6px;padding:12px;margin:8px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;color:#92400e;z-index:1000;box-shadow:0 2px 4px rgba(0,0,0,.1);";
408
- messageOverlay.innerHTML =
409
- '<div style="display:flex;align-items:center;gap:8px;">' +
410
- '<span style="font-size:16px;">⚠️</span>' +
411
- "<div>" +
412
- "<strong>Popup blocked</strong><br>" +
413
- '<span style="font-size:12px;">' +
414
- customMessage +
415
- "</span>" +
416
- "</div>" +
417
- "</div>";
418
- if (getComputedStyle(container).position === "static") {
419
- container.style.position = "relative";
557
+ if (this.iframeManager) {
558
+ this.logger.debug("Cleaning up iframe manager");
559
+ this.iframeManager.cleanup();
560
+ this.iframeManager = undefined;
420
561
  }
421
- container.appendChild(messageOverlay);
422
- setTimeout(() => {
423
- if (messageOverlay.parentNode) {
424
- messageOverlay.parentNode.removeChild(messageOverlay);
425
- }
426
- }, 10000);
427
- this.logger.info("Popup failure message displayed to user");
428
- }
429
- /**
430
- * Setup popup failure timeout
431
- */
432
- setupPopupFailureTimeout() {
433
- this.popupFailureTimeoutHandle = window.setTimeout(() => {
434
- this.logger.info("⏰ Popup failure timeout reached");
435
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
436
- detail: "Authentication timeout - popup failure scenario",
437
- });
438
- const error = new CivicAuthError("Authentication timeout - popup failure scenario", CivicAuthErrorCode.AUTH_PROCESS_TIMEOUT);
439
- this.handleAuthError(error);
440
- }, 20000); // 20 seconds
441
- }
442
- /**
443
- * Gets the container element for the auth iframe
444
- */
445
- getContainerElement() {
446
- if (typeof this.config.targetContainerElement === "string") {
447
- const element = document.getElementById(this.config.targetContainerElement);
448
- if (!element) {
449
- this.logger.warn(`Container element with ID "${this.config.targetContainerElement}" not found`);
450
- }
451
- return element;
562
+ // Reset iframe element reference (actual removal handled by IframeManager)
563
+ if (this.iframeElement) {
564
+ this.iframeElement = undefined;
452
565
  }
453
- return this.config.targetContainerElement ?? null;
566
+ if (this.authProcessTimeoutHandle) {
567
+ this.logger.debug("Clearing authentication process timeout");
568
+ window.clearTimeout(this.authProcessTimeoutHandle);
569
+ this.authProcessTimeoutHandle = undefined;
570
+ }
571
+ this.logger.debug("Resetting promise state");
572
+ this.authPromise = undefined;
573
+ this.authPromiseResolve = undefined;
574
+ this.authPromiseReject = undefined;
454
575
  }
455
- /**
456
- * Handle OAuth callback
457
- */
458
576
  async handleCallback() {
459
- this.logger.info("🔄 Handling OAuth callback", {
460
- currentUrl: window.location.href,
461
- redirectUrl: this.config.redirectUrl,
462
- });
463
577
  try {
464
578
  const callbackHandled = await handleOAuthRedirectPage({
465
579
  clientId: this.config.clientId,
@@ -471,12 +585,10 @@ export class CivicAuth {
471
585
  },
472
586
  storageAdapter: this.storage,
473
587
  });
474
- this.logger.info("📋 Callback processing result", { callbackHandled });
475
588
  if (callbackHandled) {
476
- const successSignal = document.getElementById(CIVIC_AUTH_CONSTANTS.SUCCESS_SIGNAL_ID);
477
- const errorSignal = document.getElementById(CIVIC_AUTH_CONSTANTS.ERROR_SIGNAL_ID);
589
+ const successSignal = document.getElementById(CONSTANTS.SUCCESS_SIGNAL_ID);
590
+ const errorSignal = document.getElementById(CONSTANTS.ERROR_SIGNAL_ID);
478
591
  if (successSignal) {
479
- this.logger.info("✅ Success signal found");
480
592
  let userInfo = null;
481
593
  const userInfoAttr = successSignal.getAttribute("data-user-info");
482
594
  if (userInfoAttr) {
@@ -484,7 +596,7 @@ export class CivicAuth {
484
596
  userInfo = JSON.parse(userInfoAttr);
485
597
  }
486
598
  catch (error) {
487
- this.logger.error("Failed to parse user info", { error });
599
+ this.logger.error("Failed to parse user info:", { error });
488
600
  }
489
601
  }
490
602
  this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
@@ -493,7 +605,6 @@ export class CivicAuth {
493
605
  });
494
606
  }
495
607
  else if (errorSignal) {
496
- this.logger.error("❌ Error signal found");
497
608
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
498
609
  detail: errorSignal.textContent || "Unknown error during callback",
499
610
  });
@@ -501,7 +612,6 @@ export class CivicAuth {
501
612
  }
502
613
  }
503
614
  catch (error) {
504
- this.logger.error("❌ Callback handling failed", { error });
505
615
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
506
616
  detail: error instanceof Error
507
617
  ? error.message
@@ -509,32 +619,6 @@ export class CivicAuth {
509
619
  });
510
620
  }
511
621
  }
512
- /**
513
- * Cleans up resources and event listeners
514
- */
515
- cleanup() {
516
- this.logger.info("Cleaning up authentication client");
517
- // Clean up handlers
518
- this.iframeAuthHandler?.cleanupIframe();
519
- // Clean up timeouts
520
- if (this.authProcessTimeoutHandle) {
521
- window.clearTimeout(this.authProcessTimeoutHandle);
522
- this.authProcessTimeoutHandle = undefined;
523
- }
524
- if (this.popupFailureTimeoutHandle) {
525
- window.clearTimeout(this.popupFailureTimeoutHandle);
526
- this.popupFailureTimeoutHandle = undefined;
527
- }
528
- // Reset state
529
- this.hasPopupFailed = false;
530
- this.authPromise = undefined;
531
- this.authPromiseResolve = undefined;
532
- this.authPromiseReject = undefined;
533
- // Remove message event listener
534
- if (this.messageHandler) {
535
- window.removeEventListener("message", this.messageHandler.handleMessage);
536
- }
537
- }
538
622
  /**
539
623
  * Get the current session
540
624
  */
@@ -601,66 +685,5 @@ export class CivicAuth {
601
685
  this.sessionManager = undefined;
602
686
  this.logger.info("CivicAuth destroyed");
603
687
  }
604
- /**
605
- * Handle logout
606
- */
607
- async logout() {
608
- if (!this.sessionManager) {
609
- throw new Error("SessionManager not initialized. Provide events in config.");
610
- }
611
- try {
612
- this.logger.info("🔄 Starting logout process");
613
- this.config.events?.emit(AuthEvent.SIGN_OUT_STARTED, {
614
- detail: "Logout process started",
615
- });
616
- // Get current tokens before clearing them
617
- const tokens = await retrieveTokens(this.storage);
618
- if (!tokens?.id_token) {
619
- this.logger.warn("⚠️ No ID token found, clearing local session only");
620
- await clearTokens(this.storage);
621
- await this.sessionManager.clearSession();
622
- this.config.events?.emit(AuthEvent.SIGN_OUT_COMPLETE, {
623
- detail: "Local session cleared",
624
- });
625
- return;
626
- }
627
- if (!this.endpoints) {
628
- throw new Error("OAuth endpoints not initialized");
629
- }
630
- // Generate a state for logout
631
- const state = generateState({
632
- displayMode: this.config.displayMode || "iframe",
633
- });
634
- // Generate logout URL
635
- const logoutUrl = await generateOauthLogoutUrl({
636
- clientId: this.config.clientId,
637
- redirectUrl: this.config.redirectUrl,
638
- idToken: tokens.id_token,
639
- state: state,
640
- oauthServer: this.config.oauthServerBaseUrl,
641
- });
642
- this.logger.info("🔗 Generated logout URL", {
643
- logoutUrl: logoutUrl.toString(),
644
- });
645
- // Clear local tokens and session
646
- await clearTokens(this.storage);
647
- await this.sessionManager.clearSession();
648
- // Emit logout complete event before redirect
649
- this.config.events?.emit(AuthEvent.SIGN_OUT_COMPLETE, {
650
- detail: "Logout successful",
651
- });
652
- // Redirect to logout URL
653
- this.logger.info("🌐 Redirecting to logout URL");
654
- window.location.href = logoutUrl.toString();
655
- }
656
- catch (error) {
657
- this.logger.error("❌ Logout failed", { error });
658
- this.config.events?.emit(AuthEvent.SIGN_OUT_ERROR, {
659
- detail: error instanceof Error ? error.message : String(error),
660
- });
661
- throw new CivicAuthError("Logout failed", CivicAuthErrorCode.LOGOUT_FAILED);
662
- }
663
- }
664
688
  }
665
- export { CivicAuthErrorCode } from "./types/AuthTypes.js";
666
689
  //# sourceMappingURL=CivicAuth.js.map