@@ -27,6 +27,7 @@ import {
2727 generatePortalUrl ,
2828 Role ,
2929 GeneratePortalUrlParams ,
30+ navigateToKinde ,
3031} from "@kinde/js-utils" ;
3132import * as storeState from "./store" ;
3233import React , {
@@ -39,7 +40,7 @@ import React, {
3940import { KindeContext , KindeContextProps } from "./KindeContext" ;
4041import { getRedirectUrl } from "../utils/getRedirectUrl" ;
4142import packageJson from "../../package.json" ;
42- import { ErrorProps , LogoutOptions } from "./types" ;
43+ import { ErrorProps , LogoutOptions , PopupOptions } from "./types" ;
4344import type { RefreshTokenResult } from "@kinde/js-utils" ;
4445// TODO: need to look for old token store and convert.
4546storageSettings . keyPrefix = "" ;
@@ -98,6 +99,11 @@ type KindeProviderProps = {
9899 callbacks ?: KindeCallbacks ;
99100 scope ?: string ;
100101 forceChildrenRender ?: boolean ;
102+ /**
103+ * When the application is shown in an iFrame, auth will open in a popup window.
104+ * This is the options for the popup window.
105+ */
106+ popupOptions ?: PopupOptions ;
101107} ;
102108
103109const defaultCallbacks : KindeCallbacks = {
@@ -131,6 +137,7 @@ export const KindeProvider = ({
131137 callbacks = { } ,
132138 logoutUri,
133139 forceChildrenRender = false ,
140+ popupOptions = { } ,
134141} : KindeProviderProps ) => {
135142 const mergedCallbacks = { ...defaultCallbacks , ...callbacks } ;
136143
@@ -189,9 +196,25 @@ export const KindeProvider = ({
189196 IssuerRouteTypes . login ,
190197 authProps ,
191198 ) ;
192- document . location = authUrl . url . toString ( ) ;
199+
200+ try {
201+ navigateToKinde ( {
202+ url : authUrl . url . toString ( ) ,
203+ popupOptions,
204+ handleResult : processAuthResult ,
205+ } ) ;
206+ } catch ( error ) {
207+ mergedCallbacks . onError ?.(
208+ {
209+ error : "ERR_POPUP" ,
210+ errorDescription : ( error as Error ) . message ,
211+ } ,
212+ { } ,
213+ { } as KindeContextProps ,
214+ ) ;
215+ }
193216 } ,
194- [ audience , clientId , redirectUri ] ,
217+ [ audience , clientId , redirectUri , popupOptions , mergedCallbacks ] ,
195218 ) ;
196219
197220 const register = useCallback (
@@ -231,7 +254,22 @@ export const KindeProvider = ({
231254 IssuerRouteTypes . register ,
232255 authProps ,
233256 ) ;
234- document . location = authUrl . url . toString ( ) ;
257+ try {
258+ navigateToKinde ( {
259+ url : authUrl . url . toString ( ) ,
260+ popupOptions,
261+ handleResult : processAuthResult ,
262+ } ) ;
263+ } catch ( error ) {
264+ mergedCallbacks . onError ?.(
265+ {
266+ error : "ERR_POPUP" ,
267+ errorDescription : ( error as Error ) . message ,
268+ } ,
269+ { } ,
270+ { } as KindeContextProps ,
271+ ) ;
272+ }
235273 } catch ( error ) {
236274 console . error ( "Register error:" , error ) ;
237275 mergedCallbacks . onError ?.(
@@ -244,7 +282,7 @@ export const KindeProvider = ({
244282 ) ;
245283 }
246284 } ,
247- [ redirectUri ] ,
285+ [ redirectUri , popupOptions , mergedCallbacks ] ,
248286 ) ;
249287
250288 const logout = useCallback ( async ( options ?: string | LogoutOptions ) => {
@@ -275,11 +313,32 @@ export const KindeProvider = ({
275313 } ) ;
276314
277315 await Promise . all ( [
278- storeState . memoryStorage . destroySession ( ) ,
279- storeState . localStorage . destroySession ( ) ,
316+ storeState . memoryStorage . removeSessionItem ( StorageKeys . idToken ) ,
317+ storeState . memoryStorage . removeSessionItem ( StorageKeys . accessToken ) ,
318+ storeState . memoryStorage . removeSessionItem ( StorageKeys . refreshToken ) ,
319+ storeState . localStorage . removeSessionItem ( StorageKeys . refreshToken ) ,
280320 ] ) ;
281321
282- document . location = `${ domain } /logout?${ params . toString ( ) } ` ;
322+ await storeState . localStorage . setSessionItem (
323+ storeState . LocalKeys . performingLogout ,
324+ "true" ,
325+ ) ;
326+
327+ try {
328+ await navigateToKinde ( {
329+ url : `${ domain } /logout?${ params . toString ( ) } ` ,
330+ popupOptions,
331+ } ) ;
332+ } catch ( error ) {
333+ mergedCallbacks . onError ?.(
334+ {
335+ error : "ERR_POPUP" ,
336+ errorDescription : ( error as Error ) . message ,
337+ } ,
338+ { } ,
339+ { } as KindeContextProps ,
340+ ) ;
341+ }
283342 } catch ( error ) {
284343 console . error ( "Logout error:" , error ) ;
285344 mergedCallbacks . onError ?.(
@@ -390,6 +449,89 @@ export const KindeProvider = ({
390449 [ mergedCallbacks , contextValue ] ,
391450 ) ;
392451
452+ // Function to process authentication result from popup
453+ const processAuthResult = useCallback (
454+ async ( searchParams : URLSearchParams ) => {
455+ const decoded = atob ( searchParams . get ( "state" ) || "" ) ;
456+ let returnedState : StateWithKinde ;
457+ let kindeState : KindeState ;
458+ try {
459+ returnedState = JSON . parse ( decoded ) ;
460+ kindeState = Object . assign (
461+ returnedState . kinde || { event : PromptTypes . login } ,
462+ ) ;
463+ } catch ( error ) {
464+ console . error ( "Error parsing state:" , error ) ;
465+ mergedCallbacks . onError ?.(
466+ {
467+ error : "ERR_STATE_PARSE" ,
468+ errorDescription : String ( error ) ,
469+ } ,
470+ { } ,
471+ contextValue ,
472+ ) ;
473+ returnedState = { } as StateWithKinde ;
474+ kindeState = { event : AuthEvent . login } ;
475+ }
476+ try {
477+ const codeResponse = await exchangeAuthCode ( {
478+ urlParams : searchParams ,
479+ domain,
480+ clientId,
481+ redirectURL : getRedirectUrl ( redirectUri ) ,
482+ autoRefresh : true ,
483+ onRefresh,
484+ } ) ;
485+
486+ if ( codeResponse . success ) {
487+ const user = await getUserProfile ( ) ;
488+ if ( user ) {
489+ setState ( ( val ) => ( { ...val , user, isAuthenticated : true } ) ) ;
490+ mergedCallbacks . onSuccess ?.(
491+ user ,
492+ {
493+ ...returnedState ,
494+ kinde : undefined ,
495+ } ,
496+ contextValue ,
497+ ) ;
498+ if ( mergedCallbacks . onEvent ) {
499+ mergedCallbacks . onEvent (
500+ kindeState . event ,
501+ {
502+ ...returnedState ,
503+ kinde : undefined ,
504+ } ,
505+ contextValue ,
506+ ) ;
507+ }
508+ }
509+ } else {
510+ mergedCallbacks . onError ?.(
511+ {
512+ error : "ERR_CODE_EXCHANGE" ,
513+ errorDescription : codeResponse . error ,
514+ } ,
515+ returnedState ,
516+ contextValue ,
517+ ) ;
518+ }
519+ } catch ( error ) {
520+ mergedCallbacks . onError ?.(
521+ {
522+ error : "ERR_POPUP_AUTH" ,
523+ errorDescription : String ( error ) ,
524+ } ,
525+ returnedState ,
526+ contextValue ,
527+ ) ;
528+ } finally {
529+ setState ( ( val ) => ( { ...val , isLoading : false } ) ) ;
530+ }
531+ } ,
532+ [ domain , clientId , redirectUri , onRefresh , mergedCallbacks , contextValue ] ,
533+ ) ;
534+
393535 const handleFocus = useCallback ( ( ) => {
394536 if ( document . visibilityState === "visible" && state . isAuthenticated ) {
395537 refreshToken ( { domain, clientId, onRefresh } ) . catch ( ( error ) => {
@@ -412,8 +554,6 @@ export const KindeProvider = ({
412554 await checkAuth ( { domain, clientId } ) ;
413555 initRef . current = true ;
414556 const params = new URLSearchParams ( window . location . search ) ;
415- let returnedState : StateWithKinde ;
416- let kindeState : KindeState ;
417557
418558 if ( params . has ( "error" ) ) {
419559 const errorCode = params . get ( "error" ) ;
@@ -428,6 +568,17 @@ export const KindeProvider = ({
428568 return ;
429569 }
430570
571+ if (
572+ ( await storeState . localStorage . getSessionItem (
573+ storeState . LocalKeys . performingLogout ,
574+ ) ) === "true"
575+ ) {
576+ await storeState . localStorage . removeSessionItem (
577+ storeState . LocalKeys . performingLogout ,
578+ ) ;
579+ window . close ( ) ;
580+ }
581+
431582 const hasCode = params . has ( "code" ) ;
432583 if ( ! hasCode ) {
433584 try {
@@ -447,77 +598,28 @@ export const KindeProvider = ({
447598 return ;
448599 }
449600
450- const decoded = atob ( params . get ( "state" ) || "" ) ;
451-
452- try {
453- returnedState = JSON . parse ( decoded ) ;
454- kindeState = Object . assign (
455- returnedState . kinde || { event : PromptTypes . login } ,
456- ) ;
457- } catch ( error ) {
458- console . error ( "Error parsing state:" , error ) ;
459- mergedCallbacks . onError ?.(
601+ if ( window . opener ) {
602+ const searchParams = new URLSearchParams ( window . location . search ) ;
603+ window . opener . postMessage (
460604 {
461- error : "ERR_STATE_PARSE " ,
462- errorDescription : String ( error ) ,
605+ type : "KINDE_AUTH_RESULT " ,
606+ result : Object . fromEntries ( searchParams . entries ( ) ) ,
463607 } ,
464- { } ,
465- contextValue ,
608+ window . location . origin ,
466609 ) ;
467- returnedState = { } as StateWithKinde ;
468- kindeState = { event : AuthEvent . login } ;
469- }
470- try {
471- const redirectURL = ( await storeState . memoryStorage . getSessionItem (
472- storeState . LocalKeys . redirectUri ,
473- ) ) as string ;
474-
475- const codeResponse = await exchangeAuthCode ( {
476- urlParams : new URLSearchParams ( window . location . search ) ,
477- domain,
478- clientId,
479- redirectURL : getRedirectUrl ( redirectURL || redirectUri ) ,
480- autoRefresh : true ,
481- onRefresh,
482- } ) ;
483-
484- if ( codeResponse . success ) {
485- const user = await getUserProfile ( ) ;
486- if ( user ) {
487- setState ( ( val ) => ( { ...val , user, isAuthenticated : true } ) ) ;
488- mergedCallbacks . onSuccess ?.(
489- user ,
490- {
491- ...returnedState ,
492- kinde : undefined ,
493- } ,
494- contextValue ,
495- ) ;
496- if ( mergedCallbacks . onEvent ) {
497- mergedCallbacks . onEvent (
498- kindeState . event ,
499- {
500- ...returnedState ,
501- kinde : undefined ,
502- } ,
503- contextValue ,
504- ) ;
505- }
506- }
507- } else {
508- mergedCallbacks . onError ?.(
509- {
510- error : "ERR_CODE_EXCHANGE" ,
511- errorDescription : codeResponse . error ,
512- } ,
513- returnedState ,
514- contextValue ,
515- ) ;
516- }
517- } finally {
518- setState ( ( val ) => ( { ...val , isLoading : false } ) ) ;
610+ window . close ( ) ;
519611 }
520- } , [ clientId , domain , redirectUri , mergedCallbacks , contextValue , onRefresh ] ) ;
612+ await processAuthResult ( new URLSearchParams ( window . location . search ) ) ;
613+ } , [
614+ clientId ,
615+ domain ,
616+ redirectUri ,
617+ mergedCallbacks ,
618+ contextValue ,
619+ onRefresh ,
620+ login ,
621+ processAuthResult ,
622+ ] ) ;
521623
522624 useEffect ( ( ) => {
523625 const mounted = { current : true } ;
0 commit comments