@civic/auth 0.6.0 → 0.6.1-beta.2

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 (127) hide show
  1. package/dist/nextjs/config.d.ts.map +1 -1
  2. package/dist/nextjs/config.js +3 -1
  3. package/dist/nextjs/config.js.map +1 -1
  4. package/dist/nextjs/hooks/useUserCookie.d.ts.map +1 -1
  5. package/dist/nextjs/hooks/useUserCookie.js.map +1 -1
  6. package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
  7. package/dist/nextjs/providers/NextAuthProvider.js +1 -0
  8. package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
  9. package/dist/shared/components/CivicAuthIframeContainer.js +1 -1
  10. package/dist/shared/components/CivicAuthIframeContainer.js.map +1 -1
  11. package/dist/shared/hooks/useSignIn.d.ts +9 -4
  12. package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
  13. package/dist/shared/hooks/useSignIn.js +75 -42
  14. package/dist/shared/hooks/useSignIn.js.map +1 -1
  15. package/dist/shared/lib/BrowserAuthenticationRefresher.d.ts +7 -1
  16. package/dist/shared/lib/BrowserAuthenticationRefresher.d.ts.map +1 -1
  17. package/dist/shared/lib/BrowserAuthenticationRefresher.js +15 -2
  18. package/dist/shared/lib/BrowserAuthenticationRefresher.js.map +1 -1
  19. package/dist/shared/lib/util.d.ts +1 -1
  20. package/dist/shared/lib/util.d.ts.map +1 -1
  21. package/dist/shared/lib/util.js +6 -1
  22. package/dist/shared/lib/util.js.map +1 -1
  23. package/dist/shared/providers/AuthContext.d.ts +7 -2
  24. package/dist/shared/providers/AuthContext.d.ts.map +1 -1
  25. package/dist/shared/providers/AuthContext.js.map +1 -1
  26. package/dist/shared/providers/UserProvider.d.ts +5 -1
  27. package/dist/shared/providers/UserProvider.d.ts.map +1 -1
  28. package/dist/shared/providers/UserProvider.js.map +1 -1
  29. package/dist/shared/version.d.ts +1 -1
  30. package/dist/shared/version.d.ts.map +1 -1
  31. package/dist/shared/version.js +1 -1
  32. package/dist/shared/version.js.map +1 -1
  33. package/dist/vanillajs/auth/AuthenticationEvents.d.ts.map +1 -1
  34. package/dist/vanillajs/auth/AuthenticationEvents.js +2 -2
  35. package/dist/vanillajs/auth/AuthenticationEvents.js.map +1 -1
  36. package/dist/vanillajs/auth/CivicAuth.d.ts +111 -92
  37. package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
  38. package/dist/vanillajs/auth/CivicAuth.js +474 -321
  39. package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
  40. package/dist/vanillajs/auth/SessionManager.d.ts +40 -9
  41. package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
  42. package/dist/vanillajs/auth/SessionManager.js +96 -61
  43. package/dist/vanillajs/auth/SessionManager.js.map +1 -1
  44. package/dist/vanillajs/auth/TokenRefresher.d.ts +54 -0
  45. package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -0
  46. package/dist/vanillajs/auth/TokenRefresher.js +166 -0
  47. package/dist/vanillajs/auth/TokenRefresher.js.map +1 -0
  48. package/dist/vanillajs/auth/config/ConfigProcessor.d.ts +6 -0
  49. package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -0
  50. package/dist/vanillajs/auth/config/ConfigProcessor.js +59 -0
  51. package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -0
  52. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +40 -0
  53. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -0
  54. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +388 -0
  55. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -0
  56. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +170 -0
  57. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -0
  58. package/dist/vanillajs/auth/handlers/MessageHandler.js +367 -0
  59. package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -0
  60. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.d.ts +90 -0
  61. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.d.ts.map +1 -0
  62. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js +301 -0
  63. package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js.map +1 -0
  64. package/dist/vanillajs/auth/handlers/PopupHandler.d.ts +108 -0
  65. package/dist/vanillajs/auth/handlers/PopupHandler.d.ts.map +1 -0
  66. package/dist/vanillajs/auth/handlers/PopupHandler.js +333 -0
  67. package/dist/vanillajs/auth/handlers/PopupHandler.js.map +1 -0
  68. package/dist/vanillajs/auth/types/AuthTypes.d.ts +128 -0
  69. package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -0
  70. package/dist/vanillajs/auth/types/AuthTypes.js +40 -0
  71. package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -0
  72. package/dist/vanillajs/iframe/IframeManager.d.ts +115 -0
  73. package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -0
  74. package/dist/vanillajs/iframe/IframeManager.js +614 -0
  75. package/dist/vanillajs/iframe/IframeManager.js.map +1 -0
  76. package/dist/vanillajs/iframe/IframeResizer.d.ts +15 -0
  77. package/dist/vanillajs/iframe/IframeResizer.d.ts.map +1 -0
  78. package/dist/vanillajs/iframe/IframeResizer.js +127 -0
  79. package/dist/vanillajs/iframe/IframeResizer.js.map +1 -0
  80. package/dist/vanillajs/index.d.ts +8 -7
  81. package/dist/vanillajs/index.d.ts.map +1 -1
  82. package/dist/vanillajs/index.js +10 -7
  83. package/dist/vanillajs/index.js.map +1 -1
  84. package/dist/vanillajs/services/ApiService.d.ts.map +1 -1
  85. package/dist/vanillajs/services/ApiService.js +2 -2
  86. package/dist/vanillajs/services/ApiService.js.map +1 -1
  87. package/dist/vanillajs/types/index.d.ts +17 -12
  88. package/dist/vanillajs/types/index.d.ts.map +1 -1
  89. package/dist/vanillajs/types/index.js +15 -10
  90. package/dist/vanillajs/types/index.js.map +1 -1
  91. package/dist/vanillajs/ui/LoadingComponents.d.ts +51 -0
  92. package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -0
  93. package/dist/vanillajs/ui/LoadingComponents.js +363 -0
  94. package/dist/vanillajs/ui/LoadingComponents.js.map +1 -0
  95. package/dist/vanillajs/utils/auth-utils.d.ts +2 -1
  96. package/dist/vanillajs/utils/auth-utils.d.ts.map +1 -1
  97. package/dist/vanillajs/utils/auth-utils.js +6 -3
  98. package/dist/vanillajs/utils/auth-utils.js.map +1 -1
  99. package/dist/vanillajs/utils/logger.d.ts +16 -15
  100. package/dist/vanillajs/utils/logger.d.ts.map +1 -1
  101. package/dist/vanillajs/utils/logger.js +35 -19
  102. package/dist/vanillajs/utils/logger.js.map +1 -1
  103. package/package.json +1 -1
  104. package/dist/vanillajs/iframe/domUtils.d.ts +0 -4
  105. package/dist/vanillajs/iframe/domUtils.d.ts.map +0 -1
  106. package/dist/vanillajs/iframe/domUtils.js +0 -25
  107. package/dist/vanillajs/iframe/domUtils.js.map +0 -1
  108. package/dist/vanillajs/storage/BrowserCookieStorageAdapter.d.ts +0 -19
  109. package/dist/vanillajs/storage/BrowserCookieStorageAdapter.d.ts.map +0 -1
  110. package/dist/vanillajs/storage/BrowserCookieStorageAdapter.js +0 -101
  111. package/dist/vanillajs/storage/BrowserCookieStorageAdapter.js.map +0 -1
  112. package/dist/vanillajs/storage/BrowserLocalStorageAdapter.d.ts +0 -9
  113. package/dist/vanillajs/storage/BrowserLocalStorageAdapter.d.ts.map +0 -1
  114. package/dist/vanillajs/storage/BrowserLocalStorageAdapter.js +0 -36
  115. package/dist/vanillajs/storage/BrowserLocalStorageAdapter.js.map +0 -1
  116. package/dist/vanillajs/storage/InMemoryStorageAdapter.d.ts +0 -9
  117. package/dist/vanillajs/storage/InMemoryStorageAdapter.d.ts.map +0 -1
  118. package/dist/vanillajs/storage/InMemoryStorageAdapter.js +0 -16
  119. package/dist/vanillajs/storage/InMemoryStorageAdapter.js.map +0 -1
  120. package/dist/vanillajs/storage/StorageAdapter.d.ts +0 -15
  121. package/dist/vanillajs/storage/StorageAdapter.d.ts.map +0 -1
  122. package/dist/vanillajs/storage/StorageAdapter.js +0 -16
  123. package/dist/vanillajs/storage/StorageAdapter.js.map +0 -1
  124. package/dist/vanillajs/utils/page-handlers.d.ts +0 -29
  125. package/dist/vanillajs/utils/page-handlers.d.ts.map +0 -1
  126. package/dist/vanillajs/utils/page-handlers.js +0 -165
  127. package/dist/vanillajs/utils/page-handlers.js.map +0 -1
@@ -1,88 +1,55 @@
1
- import { createIframe, removeIframe } from "../iframe/domUtils.js";
2
1
  import { AuthEvent } from "../types/index.js";
3
- import { StorageAdapterToAuthStorage, } from "../storage/StorageAdapter.js";
4
- import { BrowserCookieStorageAdapter } from "../storage/BrowserCookieStorageAdapter.js";
5
- import { handleOAuthRedirectPage } from "../utils/page-handlers.js";
6
2
  import { buildAuthUrl } from "../utils/auth-utils.js";
7
- import { createLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
8
- import { SignalObserver } from "../iframe/SignalObserver.js";
3
+ import { createMainLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
9
4
  import { GenericPublicClientPKCEProducer } from "../../services/PKCE.js";
10
5
  import { generateState } from "../../lib/oauth.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
- }
6
+ 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";
41
15
  /**
42
16
  * CivicAuth client for handling OAuth authentication
17
+ *
18
+ * This is a refactored version that uses a modular architecture for better maintainability.
43
19
  */
44
20
  export class CivicAuth {
45
21
  config;
46
- iframeElement;
47
- observer;
22
+ storage;
23
+ endpoints;
24
+ logger;
25
+ sessionManager;
26
+ initialDisplayMode;
27
+ // Authentication state
48
28
  authPromise;
49
29
  authPromiseResolve;
50
30
  authPromiseReject;
51
31
  authProcessTimeoutHandle;
52
- messageEventHandler;
53
- storage;
54
- endpoints;
55
- logger;
32
+ popupFailureTimeoutHandle;
33
+ hasPopupFailed = false;
34
+ // Handlers
35
+ messageHandler;
36
+ popupHandler;
37
+ iframeAuthHandler;
56
38
  /**
57
- * Private constructor for CivicAuth.
39
+ * Private constructor - initializes configuration and handlers.
58
40
  * Use {@link CivicAuth.create} to create a new instance.
59
- * @param config - Configuration options for the auth client
60
- * @throws {CivicAuthError} If required configuration is missing
61
- * @private
62
41
  */
63
42
  constructor(config) {
64
- const loggingConfig = {
65
- enabled: true,
66
- namespace: "iframe",
67
- level: "debug",
68
- ...config.logging,
69
- };
70
- this.config = {
71
- displayMode: "iframe",
72
- authProcessTimeout: config.authProcessTimeout || CONSTANTS.DEFAULT_AUTH_PROCESS_TIMEOUT,
73
- iframeId: config.iframeId || CONSTANTS.DEFAULT_IFRAME_ID,
74
- logging: loggingConfig,
75
- ...config,
76
- };
77
- // Configure logging based on config
78
- configureLogging(loggingConfig);
79
- // Initialize logger based on config
43
+ // Process config with defaults and validation
44
+ this.config = processConfigWithDefaults(config);
45
+ this.initialDisplayMode = this.config.displayMode;
46
+ // Configure logging
47
+ configureLogging(this.config.logging);
48
+ // Initialize logger - always use "vanillajs" as base namespace
80
49
  if (this.config.logging?.enabled) {
81
- const namespace = this.config.logging.namespace || "iframe";
82
- this.logger = createLogger(namespace);
50
+ this.logger = createMainLogger("vanillajs"); // Always use "vanillajs"
83
51
  }
84
52
  else {
85
- // Create a no-op logger when logging is disabled
86
53
  this.logger = {
87
54
  debug: () => { },
88
55
  info: () => { },
@@ -90,21 +57,14 @@ export class CivicAuth {
90
57
  error: () => { },
91
58
  };
92
59
  }
93
- // Set this logger as the current logger
94
60
  setCurrentLogger(this.logger);
95
- this.storage = config.storageAdapter || new BrowserCookieStorageAdapter();
96
- // Validate required configuration
97
- const requiredConfigs = [
98
- { key: "clientId", value: config.clientId },
99
- { key: "targetContainerElement", value: config.targetContainerElement },
100
- { key: "textSignals.success", value: config.textSignals?.success },
101
- ];
102
- for (const { key, value } of requiredConfigs) {
103
- if (!value) {
104
- throw new CivicAuthError(`CivicAuth: ${key} is required.`, CivicAuthErrorCode.CONFIG_REQUIRED);
105
- }
61
+ this.storage = this.config.storageAdapter;
62
+ // Initialize SessionManager if events are provided
63
+ if (config.events) {
64
+ this.sessionManager = new SessionManager(this.storage, config.events);
106
65
  }
107
- this.messageEventHandler = this.handleIframeMessage.bind(this);
66
+ // Initialize handlers
67
+ this.initializeHandlers();
108
68
  }
109
69
  /**
110
70
  * Creates and initializes a new instance of CivicAuth.
@@ -118,9 +78,12 @@ export class CivicAuth {
118
78
  * ```typescript
119
79
  * const auth = await CivicAuth.create({
120
80
  * clientId: "your-client-id",
121
- * redirectUrl: "https://your-app.com/callback",
122
- * oauthServerBaseUrl: "https://auth-server.com/",
123
- * scopes: ["openid", "profile"],
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
124
87
  * targetContainerElement: "auth-container",
125
88
  * textSignals: {
126
89
  * success: "Authentication successful!"
@@ -135,11 +98,16 @@ export class CivicAuth {
135
98
  }
136
99
  /**
137
100
  * Initializes the auth client and checks for callback handling
138
- * @throws {CivicAuthError} If initialization fails
139
101
  */
140
102
  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
+ });
141
109
  try {
142
- // Get OAuth endpoints from well-known configuration
110
+ // Get OAuth endpoints
143
111
  this.endpoints = {
144
112
  auth: `${this.config.oauthServerBaseUrl}auth`,
145
113
  token: `${this.config.oauthServerBaseUrl}token`,
@@ -147,16 +115,44 @@ export class CivicAuth {
147
115
  userinfo: `${this.config.oauthServerBaseUrl}userinfo`,
148
116
  endsession: `${this.config.oauthServerBaseUrl}endsession`,
149
117
  };
118
+ this.logger.info("🔗 OAuth endpoints configured", {
119
+ endpoints: this.endpoints,
120
+ });
121
+ // Initialize SessionManager with auth config
122
+ if (this.sessionManager) {
123
+ const authConfig = {
124
+ clientId: this.config.clientId,
125
+ redirectUrl: this.config.redirectUrl,
126
+ oauthServer: this.config.oauthServerBaseUrl,
127
+ scopes: this.config.scopes,
128
+ endpoints: this.endpoints,
129
+ };
130
+ this.logger.info("🔧 Initializing SessionManager", { authConfig });
131
+ await this.sessionManager.initializeWithAuthConfig(authConfig);
132
+ }
150
133
  // Check if we're on the callback page
151
- if (window.location.href.startsWith(this.config.redirectUrl)) {
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");
152
142
  await this.handleCallback();
153
143
  }
144
+ else {
145
+ this.logger.info("🏠 Not a callback page, initialization complete");
146
+ }
154
147
  }
155
148
  catch (error) {
156
149
  const errorMessage = error instanceof Error
157
150
  ? error.message
158
151
  : "Failed to initialize authentication";
159
- this.logger.error("Failed to initialize CivicAuth:", { error });
152
+ this.logger.error(" CivicAuth initialization failed", {
153
+ error: errorMessage,
154
+ stack: error instanceof Error ? error.stack : undefined,
155
+ });
160
156
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
161
157
  detail: errorMessage,
162
158
  });
@@ -164,30 +160,34 @@ export class CivicAuth {
164
160
  }
165
161
  }
166
162
  /**
167
- * Gets the container element for the auth iframe
168
- * @returns The container element or null if not found
163
+ * Initialize all handlers with proper configuration
169
164
  */
170
- getContainerElement() {
171
- if (typeof this.config.targetContainerElement === "string") {
172
- const element = document.getElementById(this.config.targetContainerElement);
173
- if (!element) {
174
- this.logger.warn(`Container element with ID "${this.config.targetContainerElement}" not found`);
175
- }
176
- return element;
177
- }
178
- return this.config.targetContainerElement;
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
+ });
179
182
  }
180
183
  /**
181
184
  * Builds the authentication URL with PKCE challenge
182
- * @returns The complete authentication URL
183
- * @throws {CivicAuthError} If endpoints are not initialized
184
185
  */
185
186
  async buildAuthUrl() {
186
187
  if (!this.endpoints) {
187
188
  throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
188
189
  }
189
- const authStorage = new StorageAdapterToAuthStorage(this.storage);
190
- const pkceProducer = new GenericPublicClientPKCEProducer(authStorage);
190
+ const pkceProducer = new GenericPublicClientPKCEProducer(this.storage);
191
191
  const codeChallenge = await pkceProducer.getCodeChallenge();
192
192
  const state = this.config.initialState ||
193
193
  generateState({
@@ -201,270 +201,265 @@ export class CivicAuth {
201
201
  codeChallenge,
202
202
  state,
203
203
  prompt: this.config.prompt,
204
+ nonce: this.config.nonce,
204
205
  });
205
206
  }
206
- handleIframeMessage(event) {
207
- const expectedOrigin = new URL(this.config.oauthServerBaseUrl).origin;
208
- this.logIncomingMessage(event, expectedOrigin);
209
- if (!this.isValidMessageSource(event, expectedOrigin)) {
210
- return;
211
- }
212
- this.handleValidMessage(event);
213
- }
214
- logIncomingMessage(event, expectedOrigin) {
215
- this.logger.debug("Global window received message:", {
216
- data: event.data,
217
- origin: event.origin,
218
- sourceProvided: !!event.source,
219
- iframeContentWindow: this.iframeElement?.contentWindow,
220
- expectedIframeOrigin: expectedOrigin,
221
- });
222
- }
223
- isValidMessageSource(event, expectedOrigin) {
224
- const isValidOrigin = event.origin === expectedOrigin;
225
- const isValidSource = event.source === this.iframeElement?.contentWindow;
226
- if (!isValidOrigin) {
227
- this.logger.warn("Ignored message from unexpected origin.", {
228
- receivedOrigin: event.origin,
229
- expectedOrigin,
230
- iframeSrc: this.iframeElement?.src,
231
- });
232
- }
233
- if (!isValidSource) {
234
- this.logger.warn("Ignored message from unexpected source.", {
235
- isSourceProvided: !!event.source,
236
- isIframeContentWindowAvailable: !!this.iframeElement?.contentWindow,
237
- iframeSrc: this.iframeElement?.src,
238
- });
239
- }
240
- return isValidOrigin && isValidSource;
241
- }
242
- handleValidMessage(event) {
243
- this.logger.info("Message from configured iframe source and origin received", {
244
- data: event.data,
245
- iframeSrc: this.iframeElement?.src,
246
- });
247
- const message = event.data;
248
- const messageType = message?.type;
249
- switch (messageType) {
250
- case "auth_success":
251
- this.handleAuthSuccess(message);
252
- break;
253
- case "auth_error":
254
- this.handleAuthError(message);
255
- break;
256
- default:
257
- this.logger.debug("Message from iframe did not match expected types (auth_success, auth_error)", { data: event.data });
258
- }
259
- }
260
- handleAuthSuccess(data) {
261
- this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
262
- detail: "Success signal received via postMessage",
263
- data,
264
- });
265
- this.authPromiseResolve?.(data?.data || {});
266
- this.cleanup();
267
- }
268
- handleAuthError(data) {
269
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
270
- detail: "Error signal received via postMessage",
271
- error: data,
272
- });
273
- this.authPromiseReject?.(new CivicAuthError(data?.detail || "Error signal received via postMessage", CivicAuthErrorCode.INVALID_MESSAGE));
274
- this.cleanup();
275
- }
276
- setupSignalObserver(iframeDoc) {
277
- const signalObserver = new SignalObserver({
278
- textSignals: this.config.textSignals,
279
- events: this.config.events,
280
- logger: this.logger,
281
- }, this.authPromiseResolve, this.authPromiseReject, () => this.cleanup());
282
- signalObserver.setup(iframeDoc);
283
- }
284
- async handleNewTabAuth(fullAuthUrl, reject) {
285
- const popupWindow = window.open(fullAuthUrl, "_blank");
286
- if (!popupWindow) {
287
- const error = new CivicAuthError("Failed to open popup window. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
288
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
289
- detail: error.message,
290
- });
291
- reject(error);
292
- }
293
- }
294
- async handleIframeAuth(fullAuthUrl, reject) {
295
- const container = this.getContainerElement();
296
- if (!container) {
297
- const error = new CivicAuthError("Target container element not found.", CivicAuthErrorCode.CONTAINER_NOT_FOUND);
298
- this.logger.error(error.message);
299
- reject(error);
300
- return;
301
- }
302
- this.logger.debug("Creating iframe", {
303
- url: fullAuthUrl,
304
- containerId: container?.id,
305
- iframeId: this.config.iframeId,
306
- origin: window.location.origin,
307
- });
308
- this.iframeElement = createIframe(fullAuthUrl, container, this.config.iframeId);
309
- this.config.events?.emit(AuthEvent.SIGN_IN_STARTED, {
310
- detail: "Iframe created",
311
- });
312
- this.iframeElement.onload = () => {
313
- this.logger.info("Iframe loaded", {
314
- iframeSrc: this.iframeElement?.src,
315
- currentOrigin: window.location.origin,
316
- expectedAuthServerOrigin: new URL(this.config.oauthServerBaseUrl)
317
- .origin,
318
- });
319
- if (!this.iframeElement?.contentWindow) {
320
- const errorMsg = "Iframe content window not available after load.";
321
- this.logger.error(errorMsg, {
322
- iframeSrc: this.iframeElement?.src,
323
- });
324
- reject(new Error(errorMsg));
325
- this.cleanup();
326
- return;
327
- }
328
- // Set up postMessage listener for cross-origin communication
329
- if (this.messageEventHandler) {
330
- window.addEventListener("message", this.messageEventHandler);
331
- this.logger.info("Added cross-origin message event listener for auth server communication", {
332
- parentOrigin: window.location.origin,
333
- authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
334
- });
335
- }
336
- else {
337
- this.logger.error("messageEventHandler was not defined when trying to add listener.");
338
- }
339
- // Try to detect redirect to our domain
340
- try {
341
- const currentIframeHref = this.iframeElement.contentWindow.location.href;
342
- if (currentIframeHref.startsWith(this.config.redirectUrl)) {
343
- this.logger.info("Iframe has navigated to redirectUrl (same-origin). Setting up DOM observer.");
344
- if (this.iframeElement.contentDocument &&
345
- this.iframeElement.contentDocument.body) {
346
- this.setupSignalObserver(this.iframeElement.contentDocument);
347
- }
348
- }
349
- }
350
- catch (error) {
351
- this.logger.error("Error checking iframe href", {
352
- error,
353
- iframeSrc: this.iframeElement?.src,
354
- });
355
- // This is expected when the iframe is on the auth server domain
356
- this.logger.info("Iframe is on auth server domain - using postMessage for communication", {
357
- parentOrigin: window.location.origin,
358
- authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
359
- });
360
- }
361
- };
362
- this.iframeElement.onerror = (event) => {
363
- this.logger.error("Iframe load error", {
364
- event,
365
- iframeSrc: this.iframeElement?.src,
366
- currentOrigin: window.location.origin,
367
- });
368
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
369
- detail: "Iframe load error",
370
- error: event,
371
- });
372
- reject(new Error("Iframe failed to load."));
373
- this.cleanup();
374
- };
375
- }
376
207
  /**
377
208
  * Starts the authentication process
378
209
  * @returns A promise that resolves with the authentication result
379
210
  * @throws {CivicAuthError} If authentication fails or times out
380
211
  */
381
212
  async startAuthentication() {
213
+ this.logger.info("🎬 Starting authentication process", {
214
+ displayMode: this.config.displayMode,
215
+ userAgent: navigator.userAgent,
216
+ currentUrl: window.location.href,
217
+ });
382
218
  if (!this.endpoints) {
383
219
  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
+ });
384
223
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
385
224
  detail: error.message,
386
225
  });
387
226
  throw error;
388
227
  }
389
228
  if (this.authPromise) {
390
- this.logger.info("Authentication already in progress, returning existing promise");
229
+ this.logger.info("Authentication already in progress, returning existing promise");
391
230
  return this.authPromise;
392
231
  }
393
232
  const fullAuthUrl = await this.buildAuthUrl();
394
- this.logger.info("Starting authentication process", {
395
- constructedIframeUrl: fullAuthUrl,
233
+ this.logger.info("🔗 Built authentication URL", {
234
+ url: fullAuthUrl,
396
235
  displayMode: this.config.displayMode,
397
236
  authProcessTimeout: this.config.authProcessTimeout,
398
237
  });
399
238
  this.authPromise = new Promise((resolve, reject) => {
400
239
  this.authPromiseResolve = resolve;
401
240
  this.authPromiseReject = reject;
402
- // Handle different display modes
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 {
403
254
  switch (this.config.displayMode) {
404
255
  case "redirect":
405
- // For redirect mode, just navigate to the auth URL
256
+ this.logger.info("🌐 Using redirect mode");
406
257
  window.location.href = fullAuthUrl;
407
258
  break;
408
259
  case "new_tab":
409
- this.handleNewTabAuth(fullAuthUrl, reject);
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);
410
265
  break;
411
266
  case "iframe":
412
- default:
413
- this.handleIframeAuth(fullAuthUrl, reject);
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);
414
274
  break;
275
+ }
415
276
  }
416
- // Set up timeout for all display modes
417
- if (this.config.authProcessTimeout &&
418
- this.config.authProcessTimeout > 0) {
419
- this.logger.debug("Setting up authentication timeout", {
420
- authProcessTimeout: this.config.authProcessTimeout,
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
+ }
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", {
421
333
  displayMode: this.config.displayMode,
334
+ currentOrigin: window.location.origin,
335
+ authProcessTimeout: this.config.authProcessTimeout,
422
336
  });
423
- this.authProcessTimeoutHandle = window.setTimeout(() => {
424
- this.logger.warn("Authentication timed out", {
425
- displayMode: this.config.displayMode,
426
- currentOrigin: window.location.origin,
427
- });
428
- this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
429
- detail: "Authentication timed out",
430
- });
431
- reject(new Error("Authentication timed out."));
432
- this.cleanup();
433
- }, this.config.authProcessTimeout);
434
- }
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,
435
368
  });
436
- return this.authPromise;
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
+ }
437
390
  }
438
391
  /**
439
- * Cleans up resources and event listeners
392
+ * Show popup failure message to user
440
393
  */
441
- cleanup() {
442
- this.logger.info("Cleaning up iframe authentication client");
443
- if (this.observer) {
444
- this.logger.debug("Disconnecting mutation observer");
445
- this.observer.disconnect();
446
- this.observer = undefined;
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;
447
399
  }
448
- if (this.messageEventHandler) {
449
- window.removeEventListener("message", this.messageEventHandler);
450
- this.logger.debug("Removed 'message' event listener from window.");
400
+ const existingMessage = document.getElementById("civic-auth-popup-failure-message");
401
+ if (existingMessage && existingMessage.parentNode) {
402
+ existingMessage.parentNode.removeChild(existingMessage);
451
403
  }
452
- if (this.iframeElement) {
453
- this.logger.debug("Removing iframe element");
454
- removeIframe(this.iframeElement);
455
- this.iframeElement = undefined;
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";
456
420
  }
457
- if (this.authProcessTimeoutHandle) {
458
- this.logger.debug("Clearing authentication process timeout");
459
- window.clearTimeout(this.authProcessTimeoutHandle);
460
- this.authProcessTimeoutHandle = undefined;
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;
461
452
  }
462
- this.logger.debug("Resetting promise state");
463
- this.authPromise = undefined;
464
- this.authPromiseResolve = undefined;
465
- this.authPromiseReject = undefined;
453
+ return this.config.targetContainerElement ?? null;
466
454
  }
455
+ /**
456
+ * Handle OAuth callback
457
+ */
467
458
  async handleCallback() {
459
+ this.logger.info("🔄 Handling OAuth callback", {
460
+ currentUrl: window.location.href,
461
+ redirectUrl: this.config.redirectUrl,
462
+ });
468
463
  try {
469
464
  const callbackHandled = await handleOAuthRedirectPage({
470
465
  clientId: this.config.clientId,
@@ -474,11 +469,14 @@ export class CivicAuth {
474
469
  success: this.config.textSignals.success,
475
470
  error: this.config.textSignals.error || "Authentication failed",
476
471
  },
472
+ storageAdapter: this.storage,
477
473
  });
474
+ this.logger.info("📋 Callback processing result", { callbackHandled });
478
475
  if (callbackHandled) {
479
- const successSignal = document.getElementById(CONSTANTS.SUCCESS_SIGNAL_ID);
480
- const errorSignal = document.getElementById(CONSTANTS.ERROR_SIGNAL_ID);
476
+ const successSignal = document.getElementById(CIVIC_AUTH_CONSTANTS.SUCCESS_SIGNAL_ID);
477
+ const errorSignal = document.getElementById(CIVIC_AUTH_CONSTANTS.ERROR_SIGNAL_ID);
481
478
  if (successSignal) {
479
+ this.logger.info("✅ Success signal found");
482
480
  let userInfo = null;
483
481
  const userInfoAttr = successSignal.getAttribute("data-user-info");
484
482
  if (userInfoAttr) {
@@ -486,7 +484,7 @@ export class CivicAuth {
486
484
  userInfo = JSON.parse(userInfoAttr);
487
485
  }
488
486
  catch (error) {
489
- this.logger.error("Failed to parse user info:", { error });
487
+ this.logger.error("Failed to parse user info", { error });
490
488
  }
491
489
  }
492
490
  this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
@@ -495,6 +493,7 @@ export class CivicAuth {
495
493
  });
496
494
  }
497
495
  else if (errorSignal) {
496
+ this.logger.error("❌ Error signal found");
498
497
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
499
498
  detail: errorSignal.textContent || "Unknown error during callback",
500
499
  });
@@ -502,6 +501,7 @@ export class CivicAuth {
502
501
  }
503
502
  }
504
503
  catch (error) {
504
+ this.logger.error("❌ Callback handling failed", { error });
505
505
  this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
506
506
  detail: error instanceof Error
507
507
  ? error.message
@@ -509,5 +509,158 @@ export class CivicAuth {
509
509
  });
510
510
  }
511
511
  }
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
+ /**
539
+ * Get the current session
540
+ */
541
+ async getCurrentSession() {
542
+ return this.sessionManager?.getCurrentSession() || null;
543
+ }
544
+ /**
545
+ * Check if user is authenticated
546
+ */
547
+ async isAuthenticated() {
548
+ return this.sessionManager?.isAuthenticated() || false;
549
+ }
550
+ /**
551
+ * Get the current user
552
+ */
553
+ async getCurrentUser() {
554
+ return this.sessionManager?.getCurrentUser() || null;
555
+ }
556
+ /**
557
+ * Clear the current session
558
+ */
559
+ async clearSession() {
560
+ if (!this.sessionManager) {
561
+ throw new Error("SessionManager not initialized. Provide events in config.");
562
+ }
563
+ return this.sessionManager.clearSession();
564
+ }
565
+ /**
566
+ * Manually refresh tokens
567
+ */
568
+ async refreshTokens() {
569
+ if (!this.sessionManager) {
570
+ throw new Error("SessionManager not initialized. Provide events in config.");
571
+ }
572
+ return this.sessionManager.refreshTokens();
573
+ }
574
+ /**
575
+ * Get token refresher state for debugging
576
+ */
577
+ getTokenRefresherState() {
578
+ return this.sessionManager?.getTokenRefresherState() || null;
579
+ }
580
+ /**
581
+ * Update the iframe display mode
582
+ * @param mode - The display mode to use for the iframe
583
+ */
584
+ setIframeDisplayMode(mode) {
585
+ this.config.iframeDisplayMode = mode;
586
+ this.logger.debug(`Iframe display mode updated to: ${mode}`);
587
+ }
588
+ /**
589
+ * Get the current iframe display mode
590
+ * @returns The current iframe display mode
591
+ */
592
+ getIframeDisplayMode() {
593
+ return this.config.iframeDisplayMode;
594
+ }
595
+ /**
596
+ * Destroy the auth client and clean up all resources
597
+ */
598
+ async destroy() {
599
+ this.cleanup();
600
+ await this.sessionManager?.destroy();
601
+ this.sessionManager = undefined;
602
+ this.logger.info("CivicAuth destroyed");
603
+ }
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
+ }
512
664
  }
665
+ export { CivicAuthErrorCode } from "./types/AuthTypes.js";
513
666
  //# sourceMappingURL=CivicAuth.js.map