@@ -215,6 +215,54 @@ export function resolvePolicies({
215215 return { policies : urlPolicies , isPoliciesResolved : true } ;
216216}
217217
218+ /**
219+ * Computes the verified state from config data and origin.
220+ * Extracted as a pure function so it can be called both synchronously
221+ * (in the loadConfig callback) and in the verified useEffect.
222+ */
223+ function computeVerifiedState (
224+ configData : Record < string , unknown > ,
225+ currentOrigin : string | undefined ,
226+ ) : boolean {
227+ const allowedOrigins = toArray ( configData . origin as string | string [ ] ) ;
228+
229+ if ( ! isIframe ( ) ) {
230+ const searchParams = new URLSearchParams ( window . location . search ) ;
231+ const redirectUrl =
232+ searchParams . get ( "redirect_url" ) || searchParams . get ( "redirect_uri" ) ;
233+
234+ if ( redirectUrl ) {
235+ try {
236+ const redirectUrlObj = new URL ( redirectUrl ) ;
237+ const redirectOrigin = redirectUrlObj . origin ;
238+ const isLocalhost =
239+ redirectOrigin . includes ( "localhost" ) ||
240+ redirectOrigin === "capacitor://localhost" ;
241+ const isOriginAllowed = isOriginVerified ( redirectUrl , allowedOrigins ) ;
242+ return isLocalhost || isOriginAllowed ;
243+ } catch ( error ) {
244+ console . error ( "Failed to parse redirect_url:" , error ) ;
245+ }
246+ }
247+
248+ return false ;
249+ }
250+
251+ if ( ! configData . origin ) {
252+ return false ;
253+ }
254+
255+ if ( currentOrigin ) {
256+ const isLocalhost =
257+ currentOrigin . includes ( "localhost" ) ||
258+ currentOrigin === "capacitor://localhost" ;
259+ const isOriginAllowed = isOriginVerified ( currentOrigin , allowedOrigins ) ;
260+ return isLocalhost || isOriginAllowed ;
261+ }
262+
263+ return false ;
264+ }
265+
218266export function useConnectionValue ( ) {
219267 const { navigate } = useNavigation ( ) ;
220268 const [ parent , setParent ] = useState < ParentMethods > ( ) ;
@@ -254,6 +302,10 @@ export function useConnectionValue() {
254302 isPoliciesResolved,
255303 isConfigLoading,
256304 } ) ;
305+ // Ref to track origin synchronously for use in async callbacks (e.g. loadConfig)
306+ // where the state value may not yet be available via closure.
307+ const originRef = useRef < string | undefined > ( undefined ) ;
308+
257309 const [ onModalClose , setOnModalCloseInternal ] = useState <
258310 ( ( ) => void ) | undefined
259311 > ( ) ;
@@ -504,7 +556,26 @@ export function useConnectionValue() {
504556 setIsConfigLoading ( true ) ;
505557 loadConfig ( urlParams . preset )
506558 . then ( ( config ) => {
507- setConfigData ( ( config as Record < string , unknown > ) || null ) ;
559+ const configObj = ( config as Record < string , unknown > ) || null ;
560+ setConfigData ( configObj ) ;
561+
562+ if ( configObj ) {
563+ // Compute verified and theme immediately so React 18 batches
564+ // all state updates into a single render, preventing a flash
565+ // of default theme / unverified domain warning.
566+ const computedVerified = computeVerifiedState (
567+ configObj ,
568+ originRef . current ,
569+ ) ;
570+ setVerified ( computedVerified ) ;
571+
572+ if ( "theme" in configObj ) {
573+ setTheme ( {
574+ verified : computedVerified ,
575+ ...( configObj . theme as ControllerTheme ) ,
576+ } ) ;
577+ }
578+ }
508579 } )
509580 . catch ( ( error : Error ) => {
510581 console . error ( "Failed to load config:" , error ) ;
@@ -515,60 +586,16 @@ export function useConnectionValue() {
515586 } ) ;
516587 } , [ urlParams . preset ] ) ;
517588
518- // Compute verified state separately once config is loaded and origin or redirect_url are available
589+ // Compute verified state when config is loaded and origin or redirect_url change.
590+ // Initial computation also happens in the loadConfig callback above to avoid
591+ // a flash, but this effect handles subsequent changes (e.g. origin arriving
592+ // later in iframe mode).
519593 useEffect ( ( ) => {
520594 if ( ! configData || isConfigLoading ) {
521595 return ;
522596 }
523597
524- const allowedOrigins = toArray ( configData . origin as string | string [ ] ) ;
525-
526- // In standalone mode (not iframe), verify preset if redirect_url matches preset whitelist
527- if ( ! isIframe ( ) ) {
528- const searchParams = new URLSearchParams ( window . location . search ) ;
529- const redirectUrl =
530- searchParams . get ( "redirect_url" ) || searchParams . get ( "redirect_uri" ) ;
531-
532- if ( redirectUrl ) {
533- try {
534- const redirectUrlObj = new URL ( redirectUrl ) ;
535- const redirectOrigin = redirectUrlObj . origin ;
536-
537- // Always consider localhost and default capacitor as verified for development
538- const isLocalhost =
539- redirectOrigin . includes ( "localhost" ) ||
540- redirectOrigin === "capacitor://localhost" ;
541- const isOriginAllowed = isOriginVerified ( redirectUrl , allowedOrigins ) ;
542- const finalVerified = isLocalhost || isOriginAllowed ;
543-
544- setVerified ( finalVerified ) ;
545- return ;
546- } catch ( error ) {
547- console . error ( "Failed to parse redirect_url:" , error ) ;
548- }
549- }
550-
551- // No redirect_url or invalid redirect_url - don't verify preset in standalone mode
552- setVerified ( false ) ;
553- return ;
554- }
555-
556- if ( ! configData . origin ) {
557- setVerified ( false ) ;
558- return ;
559- }
560-
561- // Embedded mode: verify against parent origin
562- // Always consider localhost and default capacitor as verified for development (not 127.0.0.1)
563- if ( origin ) {
564- const isLocalhost =
565- origin . includes ( "localhost" ) || origin === "capacitor://localhost" ;
566- const isOriginAllowed = isOriginVerified ( origin , allowedOrigins ) ;
567- const finalVerified = isLocalhost || isOriginAllowed ;
568- setVerified ( finalVerified ) ;
569- } else {
570- setVerified ( false ) ;
571- }
598+ setVerified ( computeVerifiedState ( configData , origin ) ) ;
572599 } , [ origin , configData , isConfigLoading ] ) ;
573600
574601 // Store referral data when URL params are available
@@ -708,7 +735,9 @@ export function useConnectionValue() {
708735
709736 connection . promise
710737 . then ( ( parentConnection ) => {
711- setOrigin ( normalizeOrigin ( parentConnection . origin ) ) ;
738+ const normalizedOrigin = normalizeOrigin ( parentConnection . origin ) ;
739+ originRef . current = normalizedOrigin ;
740+ setOrigin ( normalizedOrigin ) ;
712741 // Extract origin and spread the rest to match ParentMethods type
713742 // eslint-disable-next-line @typescript-eslint/no-unused-vars
714743 const { origin : _ , ...methods } = parentConnection ;
@@ -741,6 +770,7 @@ export function useConnectionValue() {
741770 }
742771 }
743772
773+ originRef . current = appOrigin ;
744774 setOrigin ( appOrigin ) ;
745775
746776 setParent ( {
0 commit comments