From e8964357de16d96f6082cdd325f8e4edb61b4057 Mon Sep 17 00:00:00 2001 From: Arman Mamyan Date: Thu, 22 Jan 2026 23:38:52 +0400 Subject: [PATCH] Fix: Resolve AirService init hanging by preventing concurrent initialization --- package-lock.json | 8 +++---- package.json | 2 +- src/App.tsx | 61 +++++++++++++++++++++++++++++------------------ 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40c1e56..e7b9d3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "air-credential-example", "version": "0.0.0", "dependencies": { - "@mocanetwork/airkit": "^1.6.0-beta.1", + "@mocanetwork/airkit": "^1.7.0", "jose": "^6.0.13", "react": "^19.1.0", "react-dom": "^19.1.0", @@ -1008,9 +1008,9 @@ } }, "node_modules/@mocanetwork/airkit": { - "version": "1.6.0-beta.1", - "resolved": "https://registry.npmjs.org/@mocanetwork/airkit/-/airkit-1.6.0-beta.1.tgz", - "integrity": "sha512-Qq+1fvMBHAzEsI4toKz2/h5ebGGolnGPWF8d9HjswV3PmdyycXkq1H+x7EVlPeTBy429e1WB2Rzc14fTyJyexw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@mocanetwork/airkit/-/airkit-1.7.0.tgz", + "integrity": "sha512-pdhdwdvqHqeRFBWHVKD+fZUozfc4mfnV2zeM6uJGmw+97rM3fE8Tql9Yp/miCy1zf4OZAnNGu08IqsDA0b97pg==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", diff --git a/package.json b/package.json index 0464ba4..503ae2c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "preview": "vite preview" }, "dependencies": { - "@mocanetwork/airkit": "^1.6.0-beta.1", + "@mocanetwork/airkit": "^1.7.0", "jose": "^6.0.13", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/src/App.tsx b/src/App.tsx index 0b095b8..c766698 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from "react-router-dom"; import "./App.css"; import CredentialIssuance from "./components/issuance/CredentialIssuance"; @@ -10,13 +10,31 @@ import { getEnvironmentConfig, type EnvironmentConfig } from "./config/environme // Get partner IDs from environment variables const ISSUER_PARTNER_ID = import.meta.env.VITE_ISSUER_PARTNER_ID || "66811bd6-dab9-41ef-8146-61f29d038a45"; const VERIFIER_PARTNER_ID = import.meta.env.VITE_VERIFIER_PARTNER_ID || "66811bd6-dab9-41ef-8146-61f29d038a45"; -const enableLogging = true; + +const defaultAirkitOptions: Parameters< + typeof AirService.prototype.init +>[0] = { + buildEnv: BUILD_ENV.STAGING, + enableLogging: true, + skipRehydration: false, + preloadCredential: true, +}; const ENV_OPTIONS = [ { label: "Staging", value: BUILD_ENV.STAGING }, { label: "Sandbox", value: BUILD_ENV.SANDBOX }, ]; +// Create singleton outside of component to avoid ref issues +let airServiceInstance: AirService | null = null; + +const getAirService = (partnerId: string) => { + airServiceInstance ??= new AirService({ + partnerId + }); + return airServiceInstance; +}; + // Component to get current flow title const FlowTitle = () => { const location = useLocation(); @@ -187,16 +205,16 @@ function AppRoutes({ } function App() { - const [airService, setAirService] = useState(null); const [isInitialized, setIsInitialized] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isLoggedIn, setIsLoggedIn] = useState(false); + const initAttempted = useRef(false); const [userAddress, setUserAddress] = useState(null); const [currentEnv, setCurrentEnv] = useState( - BUILD_ENV.SANDBOX + BUILD_ENV.STAGING ); const [partnerId, setPartnerId] = useState(ISSUER_PARTNER_ID); - + const airService = getAirService(partnerId); // Get environment config based on current environment const environmentConfig = getEnvironmentConfig(currentEnv); @@ -208,14 +226,15 @@ function App() { } try { - const service = new AirService({ partnerId: partnerIdToUse }); - await service.init({ buildEnv: env as (typeof BUILD_ENV)[keyof typeof BUILD_ENV], enableLogging, skipRehydration: false }); - setAirService(service); + await airService.init({ + ...defaultAirkitOptions, + buildEnv: env as (typeof BUILD_ENV)[keyof typeof BUILD_ENV] + }); setIsInitialized(true); - setIsLoggedIn(service.isLoggedIn); + setIsLoggedIn(airService.isLoggedIn); - if (service.isLoggedIn && service.loginResult) { - const result = service.loginResult; + if (airService.isLoggedIn && airService.loginResult) { + const result = airService.loginResult; console.log("result @ initializeAirService", result); if (result.abstractAccountAddress) { setUserAddress(result.abstractAccountAddress || null); @@ -242,30 +261,26 @@ function App() { setUserAddress(null); } }; - service.on(eventListener); + airService.on(eventListener); } catch (err) { console.error("Failed to initialize AIRKit service in nav bar:", err); setIsInitialized(true); // Set to true to prevent infinite loading on error } }; - // Re-initialize AIRKit when partner ID or environment changes - useEffect(() => { - initializeAirService(currentEnv, partnerId); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentEnv, partnerId]); - useEffect(() => { - // Only run on mount for initial load - // (the above effect will handle env and partner ID changes) - // eslint-disable-next-line react-hooks/exhaustive-deps + if (initAttempted.current) { + return; + } + initAttempted.current = true; initializeAirService(currentEnv, partnerId); return () => { if (airService) { airService.cleanUp(); } }; - }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentEnv, partnerId]); const handleLogin = async () => { if (!airService) return; @@ -316,4 +331,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file