@civic/auth 0.8.2 → 0.9.0-beta.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 (154) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +9 -3
  3. package/dist/constants.d.ts +2 -1
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/constants.js +3 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/lib/oauth.d.ts +4 -2
  8. package/dist/lib/oauth.d.ts.map +1 -1
  9. package/dist/lib/oauth.js +4 -2
  10. package/dist/lib/oauth.js.map +1 -1
  11. package/dist/nextjs/NextClientAuthenticationRefresher.d.ts +1 -1
  12. package/dist/nextjs/NextClientAuthenticationRefresher.d.ts.map +1 -1
  13. package/dist/nextjs/NextClientAuthenticationRefresher.js.map +1 -1
  14. package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts +1 -1
  15. package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts.map +1 -1
  16. package/dist/nextjs/NextServerAuthenticationRefresherImpl.js +3 -0
  17. package/dist/nextjs/NextServerAuthenticationRefresherImpl.js.map +1 -1
  18. package/dist/nextjs/config.d.ts +3 -0
  19. package/dist/nextjs/config.d.ts.map +1 -1
  20. package/dist/nextjs/config.js +3 -0
  21. package/dist/nextjs/config.js.map +1 -1
  22. package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
  23. package/dist/nextjs/providers/NextAuthProvider.js +1 -1
  24. package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
  25. package/dist/nextjs/routeHandler.d.ts.map +1 -1
  26. package/dist/nextjs/routeHandler.js +2 -1
  27. package/dist/nextjs/routeHandler.js.map +1 -1
  28. package/dist/reactjs/core/GlobalAuthManager.d.ts +16 -0
  29. package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
  30. package/dist/reactjs/core/GlobalAuthManager.js +28 -1
  31. package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
  32. package/dist/reactjs/hooks/useUser.d.ts +3 -0
  33. package/dist/reactjs/hooks/useUser.d.ts.map +1 -1
  34. package/dist/reactjs/hooks/useUser.js +32 -0
  35. package/dist/reactjs/hooks/useUser.js.map +1 -1
  36. package/dist/reactjs/providers/CivicAuthContext.d.ts +4 -0
  37. package/dist/reactjs/providers/CivicAuthContext.d.ts.map +1 -1
  38. package/dist/reactjs/providers/CivicAuthContext.js +22 -13
  39. package/dist/reactjs/providers/CivicAuthContext.js.map +1 -1
  40. package/dist/reactjs/providers/CivicAuthProvider.d.ts +2 -0
  41. package/dist/reactjs/providers/CivicAuthProvider.d.ts.map +1 -1
  42. package/dist/reactjs/providers/CivicAuthProvider.js +5 -1
  43. package/dist/reactjs/providers/CivicAuthProvider.js.map +1 -1
  44. package/dist/server/config.d.ts +47 -0
  45. package/dist/server/config.d.ts.map +1 -1
  46. package/dist/server/config.js.map +1 -1
  47. package/dist/server/index.d.ts +8 -2
  48. package/dist/server/index.d.ts.map +1 -1
  49. package/dist/server/index.js +5 -1
  50. package/dist/server/index.js.map +1 -1
  51. package/dist/server/login.d.ts +9 -0
  52. package/dist/server/login.d.ts.map +1 -1
  53. package/dist/server/login.js +4 -2
  54. package/dist/server/login.js.map +1 -1
  55. package/dist/server/refresh.d.ts +1 -1
  56. package/dist/server/refresh.d.ts.map +1 -1
  57. package/dist/server/refresh.js.map +1 -1
  58. package/dist/server/session.d.ts +60 -2
  59. package/dist/server/session.d.ts.map +1 -1
  60. package/dist/server/session.js +216 -5
  61. package/dist/server/session.js.map +1 -1
  62. package/dist/server/types/express.d.ts +97 -0
  63. package/dist/server/types/express.d.ts.map +1 -0
  64. package/dist/server/types/express.js +2 -0
  65. package/dist/server/types/express.js.map +1 -0
  66. package/dist/services/AuthenticationService.d.ts +12 -0
  67. package/dist/services/AuthenticationService.d.ts.map +1 -1
  68. package/dist/services/AuthenticationService.js +62 -6
  69. package/dist/services/AuthenticationService.js.map +1 -1
  70. package/dist/services/types.d.ts +1 -1
  71. package/dist/services/types.d.ts.map +1 -1
  72. package/dist/services/types.js.map +1 -1
  73. package/dist/shared/components/CivicAuthIframe.d.ts +1 -0
  74. package/dist/shared/components/CivicAuthIframe.d.ts.map +1 -1
  75. package/dist/shared/components/CivicAuthIframe.js +4 -4
  76. package/dist/shared/components/CivicAuthIframe.js.map +1 -1
  77. package/dist/shared/components/CivicAuthIframeContainer.d.ts +2 -1
  78. package/dist/shared/components/CivicAuthIframeContainer.d.ts.map +1 -1
  79. package/dist/shared/components/CivicAuthIframeContainer.js +10 -3
  80. package/dist/shared/components/CivicAuthIframeContainer.js.map +1 -1
  81. package/dist/shared/components/IFrameAndLoading.d.ts.map +1 -1
  82. package/dist/shared/components/IFrameAndLoading.js +1 -1
  83. package/dist/shared/components/IFrameAndLoading.js.map +1 -1
  84. package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
  85. package/dist/shared/hooks/useSignIn.js +5 -3
  86. package/dist/shared/hooks/useSignIn.js.map +1 -1
  87. package/dist/shared/lib/AuthenticationRefresherImpl.d.ts +2 -2
  88. package/dist/shared/lib/AuthenticationRefresherImpl.d.ts.map +1 -1
  89. package/dist/shared/lib/AuthenticationRefresherImpl.js +3 -0
  90. package/dist/shared/lib/AuthenticationRefresherImpl.js.map +1 -1
  91. package/dist/shared/lib/GenericAuthenticationRefresher.d.ts +2 -2
  92. package/dist/shared/lib/GenericAuthenticationRefresher.d.ts.map +1 -1
  93. package/dist/shared/lib/GenericAuthenticationRefresher.js.map +1 -1
  94. package/dist/shared/lib/iframeUtils.d.ts +2 -0
  95. package/dist/shared/lib/iframeUtils.d.ts.map +1 -1
  96. package/dist/shared/lib/iframeUtils.js +12 -0
  97. package/dist/shared/lib/iframeUtils.js.map +1 -1
  98. package/dist/shared/lib/types.d.ts +1 -0
  99. package/dist/shared/lib/types.d.ts.map +1 -1
  100. package/dist/shared/lib/types.js.map +1 -1
  101. package/dist/shared/lib/util.d.ts +7 -0
  102. package/dist/shared/lib/util.d.ts.map +1 -1
  103. package/dist/shared/lib/util.js +12 -0
  104. package/dist/shared/lib/util.js.map +1 -1
  105. package/dist/shared/providers/CivicAuthConfigContext.d.ts +2 -1
  106. package/dist/shared/providers/CivicAuthConfigContext.d.ts.map +1 -1
  107. package/dist/shared/providers/CivicAuthConfigContext.js +3 -1
  108. package/dist/shared/providers/CivicAuthConfigContext.js.map +1 -1
  109. package/dist/shared/version.d.ts +1 -1
  110. package/dist/shared/version.d.ts.map +1 -1
  111. package/dist/shared/version.js +1 -1
  112. package/dist/shared/version.js.map +1 -1
  113. package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts +41 -0
  114. package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts.map +1 -0
  115. package/dist/vanillajs/auth/BackendAuthenticationRefresher.js +125 -0
  116. package/dist/vanillajs/auth/BackendAuthenticationRefresher.js.map +1 -0
  117. package/dist/vanillajs/auth/CivicAuth.d.ts +67 -0
  118. package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
  119. package/dist/vanillajs/auth/CivicAuth.js +310 -10
  120. package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
  121. package/dist/vanillajs/auth/SessionManager.d.ts +31 -3
  122. package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
  123. package/dist/vanillajs/auth/SessionManager.js +253 -22
  124. package/dist/vanillajs/auth/SessionManager.js.map +1 -1
  125. package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -1
  126. package/dist/vanillajs/auth/TokenRefresher.js +31 -18
  127. package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
  128. package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -1
  129. package/dist/vanillajs/auth/config/ConfigProcessor.js +15 -8
  130. package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
  131. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +44 -0
  132. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -1
  133. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +163 -1
  134. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -1
  135. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +23 -0
  136. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
  137. package/dist/vanillajs/auth/handlers/MessageHandler.js +59 -2
  138. package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
  139. package/dist/vanillajs/auth/types/AuthTypes.d.ts +20 -0
  140. package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -1
  141. package/dist/vanillajs/auth/types/AuthTypes.js +1 -0
  142. package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -1
  143. package/dist/vanillajs/iframe/IframeManager.d.ts +36 -0
  144. package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
  145. package/dist/vanillajs/iframe/IframeManager.js +216 -24
  146. package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
  147. package/dist/vanillajs/index.d.ts +2 -0
  148. package/dist/vanillajs/index.d.ts.map +1 -1
  149. package/dist/vanillajs/index.js +4 -0
  150. package/dist/vanillajs/index.js.map +1 -1
  151. package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -1
  152. package/dist/vanillajs/ui/LoadingComponents.js +1 -1
  153. package/dist/vanillajs/ui/LoadingComponents.js.map +1 -1
  154. package/package.json +7 -2
@@ -8,7 +8,7 @@ import { AuthenticationEvents } from "./AuthenticationEvents.js";
8
8
  import { PopupError } from "../../services/types.js";
9
9
  import { getVersion } from "../../shared/index.js";
10
10
  import { handleOAuthRedirectPage } from "./handlers/OAuthCallbackHandler.js";
11
- import { generateOauthLogoutUrl, clearTokens, retrieveTokens, } from "../../shared/lib/util.js";
11
+ import { generateOauthLogoutUrl, clearTokens, retrieveTokens, getBackendEndpoints, } from "../../shared/lib/util.js";
12
12
  import { LOGOUT_STATE } from "../../constants.js";
13
13
  import { getOauthEndpoints } from "../../lib/oauth.js";
14
14
  import { collectAndSendSDKAnalytics } from "../../lib/analytics.js";
@@ -17,6 +17,7 @@ import { processConfigWithDefaults } from "./config/ConfigProcessor.js";
17
17
  import { MessageHandler } from "./handlers/MessageHandler.js";
18
18
  import { PopupHandler } from "./handlers/PopupHandler.js";
19
19
  import { IframeAuthHandler } from "./handlers/IframeAuthHandler.js";
20
+ import { IframeManager } from "../iframe/IframeManager.js";
20
21
  /**
21
22
  * CivicAuth client for handling OAuth authentication
22
23
  *
@@ -37,6 +38,9 @@ export class CivicAuth {
37
38
  authProcessTimeoutHandle;
38
39
  popupFailureTimeoutHandle;
39
40
  hasPopupFailed = false;
41
+ hasSignInStarted = false;
42
+ // Backend integration - custom login URL
43
+ loginUrl;
40
44
  // Handlers
41
45
  messageHandler;
42
46
  popupHandler;
@@ -49,6 +53,8 @@ export class CivicAuth {
49
53
  // Process config with defaults and validation
50
54
  this.config = processConfigWithDefaults(config);
51
55
  this.initialDisplayMode = this.config.displayMode;
56
+ // Set loginUrl from config if provided
57
+ this.loginUrl = this.config.loginUrl;
52
58
  // Configure logging
53
59
  configureLogging(this.config.logging);
54
60
  // Initialize logger - always use "vanillajs" as base namespace
@@ -68,7 +74,7 @@ export class CivicAuth {
68
74
  // Always initialize events - use provided events or create default instance
69
75
  this.events = config.events || new AuthenticationEvents();
70
76
  // Always initialize SessionManager
71
- this.sessionManager = new SessionManager(this.storage, this.events);
77
+ this.sessionManager = new SessionManager(this.storage, this.events, this.config);
72
78
  // Initialize handlers
73
79
  this.initializeHandlers();
74
80
  }
@@ -82,6 +88,7 @@ export class CivicAuth {
82
88
  *
83
89
  * @example
84
90
  * ```typescript
91
+ * // Standard SPA authentication
85
92
  * const auth = await CivicAuth.create({
86
93
  * clientId: "your-client-id",
87
94
  * // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
@@ -97,6 +104,12 @@ export class CivicAuth {
97
104
  * success: "Authentication successful!"
98
105
  * }
99
106
  * });
107
+ *
108
+ * // Backend integration authentication
109
+ * const authWithBackend = await CivicAuth.create({
110
+ * clientId: "your-client-id",
111
+ * loginUrl: "http://example.com/custom-backendurl" // Automatically uses BrowserCookieStorage
112
+ * });
100
113
  * ```
101
114
  */
102
115
  static async create(config) {
@@ -128,6 +141,8 @@ export class CivicAuth {
128
141
  oauthServer: this.config.oauthServerBaseUrl,
129
142
  scopes: this.config.scopes,
130
143
  endpoints: this.endpoints,
144
+ loginUrl: this.config.loginUrl || this.loginUrl, // Include loginUrl for backend flows
145
+ backendEndpoints: this.config.backendEndpoints, // Pass backend endpoints config
131
146
  };
132
147
  this.logger.info("🔧 Initializing SessionManager", { authConfig });
133
148
  await this.sessionManager.initializeWithAuthConfig(authConfig);
@@ -148,6 +163,43 @@ export class CivicAuth {
148
163
  else {
149
164
  this.logger.info("🏠 Not a callback page, initialization complete");
150
165
  }
166
+ // Automatically preload iframe if enabled and user is not authenticated
167
+ // This runs after both callback processing and normal initialization
168
+ this.logger.info("🔍 Checking auto-preload conditions", {
169
+ preloadIframe: this.config.preloadIframe,
170
+ displayMode: this.config.displayMode,
171
+ });
172
+ if (this.config.preloadIframe && this.config.displayMode === "iframe") {
173
+ try {
174
+ const isAuthenticated = await this.isAuthenticated();
175
+ this.logger.info("🔍 Authentication check for preload", {
176
+ isAuthenticated,
177
+ });
178
+ if (!isAuthenticated) {
179
+ this.logger.info("🔄 Auto-preloading iframe for instant sign-in...");
180
+ await this.preloadAuthentication();
181
+ this.logger.info("✅ Iframe auto-preloaded successfully");
182
+ }
183
+ else {
184
+ this.logger.debug("ℹ️ User already authenticated, skipping preload");
185
+ }
186
+ }
187
+ catch (error) {
188
+ // Don't fail initialization if preloading fails - graceful degradation
189
+ this.logger.warn("⚠️ Auto-preloading failed (graceful degradation):", {
190
+ error: error instanceof Error ? error.message : String(error),
191
+ });
192
+ }
193
+ }
194
+ else {
195
+ this.logger.info("ℹ️ Auto-preload conditions not met", {
196
+ preloadIframe: this.config.preloadIframe,
197
+ displayMode: this.config.displayMode,
198
+ reason: !this.config.preloadIframe
199
+ ? "preloadIframe is disabled"
200
+ : "displayMode is not iframe",
201
+ });
202
+ }
151
203
  }
152
204
  catch (error) {
153
205
  const errorMessage = error instanceof Error
@@ -177,17 +229,77 @@ export class CivicAuth {
177
229
  this.messageHandler = new MessageHandler({
178
230
  ...handlerConfig,
179
231
  onPopupFailure: this.handlePopupFailure.bind(this),
232
+ onBrowserCorsFailsSilently: async () => {
233
+ if (this.hasSignInStarted) {
234
+ await this.handleBrowserCorsFailsSilently();
235
+ }
236
+ },
180
237
  });
181
238
  this.popupHandler = new PopupHandler(handlerConfig);
182
239
  this.iframeAuthHandler = new IframeAuthHandler({
183
240
  ...handlerConfig,
184
241
  messageHandler: this.messageHandler.handleMessage,
185
242
  });
243
+ // Set up automatic re-preloading when authentication is cancelled
244
+ this.setupAutoRepreload();
245
+ }
246
+ /**
247
+ * Set up automatic re-preloading when authentication is cancelled by user
248
+ * This maintains instant sign-in experience for subsequent attempts
249
+ */
250
+ setupAutoRepreload() {
251
+ this.events.on(AuthEvent.SIGN_IN_STARTED, () => {
252
+ this.hasSignInStarted = true;
253
+ });
254
+ this.events.on(AuthEvent.SIGN_IN_ERROR, (event) => {
255
+ // Only re-preload for iframe mode with user cancellation and preload enabled
256
+ if (this.config.displayMode === "iframe" &&
257
+ this.config.preloadIframe &&
258
+ event?.detail === "Authentication cancelled by user") {
259
+ this.logger.debug("🔄 Authentication cancelled, scheduling re-preload for instant subsequent sign-in");
260
+ // Small delay to ensure cleanup is complete
261
+ setTimeout(async () => {
262
+ try {
263
+ await this.preloadAuthentication();
264
+ this.logger.debug("✅ Re-preloaded iframe after cancellation for instant sign-in");
265
+ }
266
+ catch (error) {
267
+ // Don't fail silently, but don't crash either - just log the issue
268
+ this.logger.warn("⚠️ Failed to re-preload iframe after cancellation:", {
269
+ error: error instanceof Error ? error.message : String(error),
270
+ });
271
+ }
272
+ }, 200); // Small delay to ensure cleanup completion
273
+ }
274
+ });
186
275
  }
187
276
  /**
188
277
  * Builds the authentication URL with PKCE challenge
189
278
  */
190
279
  async buildAuthUrl() {
280
+ // If a login URL is set (for backend integration), use that instead
281
+ if (this.loginUrl) {
282
+ this.logger.info("🔗 Using login URL for backend integration", {
283
+ loginUrl: this.loginUrl,
284
+ });
285
+ // Generate state with display mode information for backend integration
286
+ const state = this.config.initialState ||
287
+ generateState({
288
+ displayMode: this.config.displayMode || "iframe",
289
+ iframeDisplayMode: this.config.iframeDisplayMode,
290
+ framework: this.config.framework || "vanillajs",
291
+ sdkVersion: getVersion(),
292
+ });
293
+ // Append state as query parameter to loginUrl
294
+ const url = new URL(this.loginUrl, window.location.origin);
295
+ url.searchParams.set("state", state);
296
+ this.logger.info("🔗 Built login URL with display mode state", {
297
+ loginUrl: url.toString(),
298
+ displayMode: this.config.displayMode,
299
+ iframeDisplayMode: this.config.iframeDisplayMode,
300
+ });
301
+ return url.toString();
302
+ }
191
303
  if (!this.endpoints) {
192
304
  throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
193
305
  }
@@ -196,6 +308,7 @@ export class CivicAuth {
196
308
  const state = this.config.initialState ||
197
309
  generateState({
198
310
  displayMode: this.config.displayMode || "iframe",
311
+ iframeDisplayMode: this.config.iframeDisplayMode,
199
312
  framework: this.config.framework || "vanillajs",
200
313
  sdkVersion: getVersion(),
201
314
  });
@@ -210,6 +323,102 @@ export class CivicAuth {
210
323
  nonce: this.config.nonce,
211
324
  });
212
325
  }
326
+ /**
327
+ * Preloads the authentication iframe for instant sign-in
328
+ * This creates the iframe in the background but keeps it hidden until startAuthentication() is called
329
+ * @throws {CivicAuthError} If preloading fails or is not supported
330
+ * @private - This method is used internally for automatic preloading
331
+ */
332
+ async preloadAuthentication() {
333
+ this.logger.info("🎬 Preloading authentication for instant sign-in", {
334
+ displayMode: this.config.displayMode,
335
+ userAgent: navigator.userAgent,
336
+ currentUrl: window.location.href,
337
+ });
338
+ if (!this.endpoints) {
339
+ const error = new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
340
+ this.logger.error("❌ Endpoints not initialized for preload", {
341
+ error: error.message,
342
+ });
343
+ throw error;
344
+ }
345
+ // Only support preloading for iframe mode
346
+ if (this.config.displayMode !== "iframe") {
347
+ this.logger.warn("⚠️ Iframe preloading only supported for iframe display mode", {
348
+ displayMode: this.config.displayMode,
349
+ });
350
+ return;
351
+ }
352
+ if (!this.iframeAuthHandler) {
353
+ const error = new CivicAuthError("Iframe handler not initialized for preloading", CivicAuthErrorCode.INIT_FAILED);
354
+ this.logger.error("❌ Iframe handler not initialized", {
355
+ error: error.message,
356
+ });
357
+ throw error;
358
+ }
359
+ const fullAuthUrl = await this.buildAuthUrl();
360
+ this.logger.info("🔗 Built authentication URL for preload", {
361
+ url: fullAuthUrl,
362
+ displayMode: this.config.displayMode,
363
+ });
364
+ try {
365
+ await this.iframeAuthHandler.preloadIframe(fullAuthUrl);
366
+ const iframeElement = this.iframeAuthHandler.getIframeElement();
367
+ if (iframeElement) {
368
+ this.messageHandler?.updateIframeElement(iframeElement);
369
+ }
370
+ this.logger.info("✅ Authentication iframe preloaded successfully");
371
+ }
372
+ catch (error) {
373
+ const errorMessage = error instanceof Error ? error.message : "Failed to preload iframe";
374
+ this.logger.error("❌ Failed to preload authentication iframe", {
375
+ error: errorMessage,
376
+ });
377
+ throw new CivicAuthError(errorMessage, CivicAuthErrorCode.IFRAME_PRELOAD_FAILED);
378
+ }
379
+ }
380
+ /**
381
+ * Check if authentication is preloaded and ready for instant sign-in
382
+ * @returns True if an iframe is preloaded and ready
383
+ */
384
+ isAuthenticationPreloaded() {
385
+ if (this.config.displayMode !== "iframe" || !this.iframeAuthHandler) {
386
+ return false;
387
+ }
388
+ return this.iframeAuthHandler.hasPreloadedIframe();
389
+ }
390
+ /**
391
+ * Enable or disable iframe preloading
392
+ * @param enabled Whether to enable iframe preloading
393
+ */
394
+ setPreloadEnabled(enabled) {
395
+ // Preloading only makes sense for iframe mode
396
+ if (this.config.displayMode !== "iframe") {
397
+ this.logger.debug("🎯 Iframe preloading not applicable for non-iframe mode", {
398
+ displayMode: this.config.displayMode,
399
+ enabled,
400
+ });
401
+ return;
402
+ }
403
+ if (this.iframeAuthHandler) {
404
+ this.iframeAuthHandler.setPreloadEnabled(enabled);
405
+ this.logger.info("🎯 Iframe preloading", { enabled });
406
+ }
407
+ else {
408
+ this.logger.warn("⚠️ Cannot set preload enabled: iframe handler not initialized");
409
+ }
410
+ }
411
+ /**
412
+ * Check if iframe preloading is enabled
413
+ * @returns True if iframe preloading is enabled
414
+ */
415
+ getPreloadEnabled() {
416
+ // Preloading only makes sense for iframe mode
417
+ if (this.config.displayMode !== "iframe") {
418
+ return false;
419
+ }
420
+ return this.iframeAuthHandler?.getPreloadEnabled() ?? false;
421
+ }
213
422
  /**
214
423
  * Starts the authentication process
215
424
  * @returns A promise that resolves with the authentication result
@@ -252,6 +461,30 @@ export class CivicAuth {
252
461
  });
253
462
  return this.authPromise;
254
463
  }
464
+ async handleBrowserCorsFailsSilently() {
465
+ this.logger.warn("🚨 Browser CORS fails silently - switching to redirect mode", {
466
+ displayMode: this.config.displayMode,
467
+ iframeAuthHandler: !!this.iframeAuthHandler,
468
+ messageHandler: !!this.messageHandler,
469
+ });
470
+ if (!this.iframeAuthHandler || !this.messageHandler) {
471
+ throw new Error("Iframe handler not initialized");
472
+ }
473
+ this.iframeAuthHandler.forceHideIframe();
474
+ const rebuiltUrl = await this.buildAuthUrl();
475
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
476
+ detail: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
477
+ error: {
478
+ type: "browser_cors_fails_silently",
479
+ failedUrl: IframeManager.browserCorsFailsSilentlyUrl,
480
+ suggestion: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
481
+ },
482
+ });
483
+ // use a small delay to ensure the loader is shown
484
+ setTimeout(() => {
485
+ window.location.href = rebuiltUrl;
486
+ }, 100);
487
+ }
255
488
  /**
256
489
  * Handle authentication based on display mode
257
490
  */
@@ -278,6 +511,9 @@ export class CivicAuth {
278
511
  if (!this.iframeAuthHandler || !this.messageHandler) {
279
512
  throw new Error("Iframe handler not initialized");
280
513
  }
514
+ if (IframeManager.browserCorsFailsSilentlyUrl) {
515
+ return this.handleBrowserCorsFailsSilently();
516
+ }
281
517
  const iframeElement = await this.iframeAuthHandler.handleIframeAuth(fullAuthUrl);
282
518
  this.messageHandler.updateIframeElement(iframeElement);
283
519
  break;
@@ -341,19 +577,15 @@ export class CivicAuth {
341
577
  });
342
578
  return;
343
579
  }
580
+ // Set up timeout for other modes
344
581
  if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
345
582
  this.logger.debug("⏰ Setting up authentication timeout", {
346
- authProcessTimeout: this.config.authProcessTimeout,
583
+ timeout: this.config.authProcessTimeout,
347
584
  displayMode: this.config.displayMode,
348
585
  iframeDisplayMode: this.config.iframeDisplayMode,
349
586
  });
350
587
  this.authProcessTimeoutHandle = window.setTimeout(() => {
351
- this.logger.error("⏰ Authentication timed out", {
352
- displayMode: this.config.displayMode,
353
- iframeDisplayMode: this.config.iframeDisplayMode,
354
- currentOrigin: window.location.origin,
355
- authProcessTimeout: this.config.authProcessTimeout,
356
- });
588
+ this.logger.info("⏰ Authentication timeout reached");
357
589
  this.events.emit(AuthEvent.SIGN_IN_ERROR, {
358
590
  detail: "Authentication timed out",
359
591
  });
@@ -599,6 +831,54 @@ export class CivicAuth {
599
831
  getTokenRefresherState() {
600
832
  return this.sessionManager.getTokenRefresherState() || null;
601
833
  }
834
+ /**
835
+ * Set a custom login URL for backend integration.
836
+ * This is useful when integrating with a backend that handles the OAuth flow.
837
+ * Alternatively, you can configure this directly in CivicAuth.create().
838
+ *
839
+ * @param loginUrl - The custom login URL to use (e.g., "http://localhost:3020/auth/login")
840
+ *
841
+ * @example
842
+ * ```typescript
843
+ * // Option 1: Configure in create()
844
+ * const authClient = await CivicAuth.create({
845
+ * clientId: "YOUR_CLIENT_ID",
846
+ * loginUrl: "http://example.com/custom-backendurl"
847
+ * });
848
+ *
849
+ * // Option 2: Set after creation
850
+ * civicAuth.setLoginUrl("http://localhost:3020/auth/login");
851
+ * await civicAuth.startAuthentication();
852
+ * ```
853
+ */
854
+ setLoginUrl(loginUrl) {
855
+ this.loginUrl = loginUrl;
856
+ // Update MessageHandler to expect messages from the custom origin
857
+ if (this.messageHandler) {
858
+ this.messageHandler.setCustomExpectedOrigin(loginUrl);
859
+ }
860
+ this.logger.info("🔗 Custom login URL set for backend integration", {
861
+ loginUrl: this.loginUrl,
862
+ });
863
+ }
864
+ /**
865
+ * Clear the login URL and return to standard OAuth flow
866
+ */
867
+ clearLoginUrl() {
868
+ this.loginUrl = undefined;
869
+ // Clear custom expected origin from MessageHandler
870
+ if (this.messageHandler) {
871
+ this.messageHandler.clearCustomExpectedOrigin();
872
+ }
873
+ this.logger.info("🔗 Login URL cleared, returning to standard OAuth flow");
874
+ }
875
+ /**
876
+ * Get the current login URL
877
+ * @returns The current login URL or undefined if not set
878
+ */
879
+ getLoginUrl() {
880
+ return this.loginUrl;
881
+ }
602
882
  /**
603
883
  * Update the iframe display mode
604
884
  * @param mode - The display mode to use for the iframe
@@ -631,7 +911,27 @@ export class CivicAuth {
631
911
  this.events.emit(AuthEvent.SIGN_OUT_STARTED, {
632
912
  detail: "Logout process started",
633
913
  });
634
- // Get current tokens before clearing them
914
+ // If a loginUrl is configured, redirect to the backend's logout endpoint.
915
+ // The backend will then handle redirecting to the OIDC provider.
916
+ if (this.loginUrl) {
917
+ this.logger.info("🚪 Redirecting to backend for logout in backend-integrated flow");
918
+ const backendUrl = new URL(this.loginUrl).origin;
919
+ const endpoints = getBackendEndpoints(this.config.backendEndpoints);
920
+ const backendLogoutUrl = `${backendUrl}${endpoints.logout}`;
921
+ // Clear local SDK session state before redirecting
922
+ await this.sessionManager.clearSession();
923
+ this.events.emit(AuthEvent.SIGN_OUT_COMPLETE, {
924
+ detail: "Local session cleared, redirecting to backend for logout.",
925
+ });
926
+ // Perform top-level redirect to the backend logout endpoint
927
+ this.logger.info("🌐 Redirecting to backend logout endpoint", {
928
+ url: backendLogoutUrl,
929
+ });
930
+ window.location.href = backendLogoutUrl;
931
+ return;
932
+ }
933
+ // --- Existing SPA redirect-based logout flow ---
934
+ this.logger.info("🚪 Using redirect-based logout for SPA flow");
635
935
  const tokens = await retrieveTokens(this.storage);
636
936
  if (!tokens?.id_token) {
637
937
  this.logger.warn("⚠️ No ID token found, clearing local session only");